簡 述: 在 Linux 中,使用互斥量(互斥鎖🔐) Mutex 來給保證多線程 ,在訪問公共變量的時候能夠 “串行” 代碼。從而使得多線程正確的同步執行。關於多線程創建和使用可以參考前面幾篇的文章,爭取早日把 Linux 系統篇之 系統編程給發佈完系列的教程。
PS:好幾天沒有接着學習 Linux 的系統函數和理論知識了。可能是前面幾天有點忙了吧;時間流逝啊,總是這麼得快,,,,
關於互斥鎖的使用如下:
- pthread_mutex_t *mutex; //創建一個鎖
- pthread_mutex_init(); //初始化一個互斥鎖
- pthread_mutex_lock(); //上鎖🔓,或者使用 pthread_mutex_trylock()
- pthread_mutex_unlock(); //解鎖🔐
- pthread_mutex_destroy; //銷燬互斥鎖
編程環境:
💻: uos20
📎 gcc/g++ 8.3
📎 gdb8.0
💻: MacOS 10.14
📎 gcc/g++ 9.2
📎 gdb8.3
背景鋪墊:
-
程序:
先寫一個多線程程序,A 和 B 兩個子線程一起數出輸出到 100000;然後在沒有鎖🔐的情況下,每一線程輪流執行 10 毫秒,模擬時間片輪轉切片。
-
代碼程序:
這個代碼還沒有加鎖,保護共享變量。完整代碼如下;
#include <stdio.h> #include <unistd.h> #include <pthread.h> int g_num = 0; //在全局區域,共享 #define MAXNUMBER 100000 void* funA(void* arg); void* funB(void* arg); int main(int argc, char *argv[]) { pthread_t pthreadA = 0; pthread_t pthreadB = 0; pthread_create(&pthreadA, nullptr, funA, nullptr); //創建兩個子線程 pthread_create(&pthreadB, nullptr, funB, nullptr); pthread_join(pthreadA, nullptr); //阻塞,回收資源 pthread_join(pthreadB, nullptr); return 0; } void* funA(void* arg) { for (int i = 0; g_num < MAXNUMBER; i++) { int a = g_num; //27-29行,增加寄存器和內存之間的數據交換操作,使得出現內存沒來得及保存數據的現象的概率大一點 a++; g_num = a; printf("A thread id: %ld, num = %d\n", pthread_self(), g_num); usleep(10); //沉睡 10 毫秒, 模擬時間片輪轉,效果更明顯 } return nullptr; } void* funB(void* arg) { for (int i = 0; g_num < MAXNUMBER; i++) { int b = g_num; b++; g_num = b; printf("B thread id: %ld, num = %d\n", pthread_self(), g_num); usleep(10); //沉睡 10 毫秒, 模擬時間片輪轉,效果更明顯 } return nullptr; }
-
結果分析:
下面截圖裏面,在 MacOS 10.14.6 和 Ubuntu 20.04 裏面跑,都是正確的結果;
[疑惑]可能是因爲系統系統內核或者底層作了修改???又或者是數數的間隔過小,數的數字不多? 預判的結果是 實際數數的大小會比實際正確結果要小一點(原因是時間片輪轉的原因),待日後挖墳考古?難道是系統比較新?所以連續出現幾次都是最正確的結果。。。
[解答]沒有出現內存來不及保存增加的數據,cpu 的時間片就被其他資源佔去了,導致最後出現的小數有可能出現在大數的後面打印,這是一個概率問題;而 27-29 行和 42-44 行,是刻意增加寄存器和內存之間的數據交換操作次數,使得出現內存沒來得及保存數據的現象的概率大一點。
-
如果在 Ubuntu 16.04 裏面跑改程序,如果出現了最後一個數字比 100000 小一點,那麼解釋的可能如下:
其中會發現結果有點混亂。這是兩個子線程互相操作同一個數據,時間片切換的時候,還新的數據還沒有來得及保存進內存裏面,就切換到了下一個線程中去了。
- [實際結果]多數了一位,最後一個結果是100001,且有時候還會下面還會再打印一個小的數字
- [期望結果]只數數到 100000;
- 分析圖如下:
-
使用互斥量(鎖) Mutex:
如果我們想使用互斥鎖同步線程,那麼所有線程,只要是有訪問同一個共享公共變量的線程都要加鎖。
- 使用流程步驟:
-
創建一把互斥鎖
pthread_mutex_t *mutex
-
對互斥鎖進程初始化 init
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
-
使用互斥鎖:每個子線程要訪問公共變量的時候,都加上鎖,使用完後就解鎖
//若果沒有上鎖,就上鎖;若果已經上鎖,直接返回,不阻塞 int pthread_mutex_trylock(pthread_mutex_t *mutex); //若果沒有上鎖,就上鎖;若果已經上鎖,就阻塞該線程 int pthread_mutex_lock(pthread_mutex_t *mutex); //解鎖 int pthread_mutex_unlock(pthread_mutex_t *mutex);
-
使用完畢,銷燬互斥鎖
int pthread_mutex_destroy(pthread_mutex_t *mutex);
改寫例子,使用互斥量(鎖)實例:
將上面的例子改了改,然後在 A、B 兩個子線程裏面,使用互斥量(鎖),會發現也會得到取其正確的結果。結果符合預期。
- 修改後的加鎖例子:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
int g_num = 0; //在全局區域,共享
#define MAXNUMBER 100000
pthread_mutex_t g_mutex; //創建全局的互斥鎖
void* funA(void* arg);
void* funB(void* arg);
int main(int argc, char *argv[])
{
pthread_mutex_init(&g_mutex, nullptr); //對鎖進行初始化
pthread_t pthreadA = 0;
pthread_t pthreadB = 0;
pthread_create(&pthreadA, nullptr, funA, nullptr); //創建兩個子線程
pthread_create(&pthreadB, nullptr, funB, nullptr);
pthread_join(pthreadA, nullptr); //阻塞,回收資源
pthread_join(pthreadB, nullptr);
pthread_mutex_destroy(&g_mutex); //釋放互斥鎖資源
return 0;
}
void* funA(void* arg)
{
for (int i = 0; g_num < MAXNUMBER; i++) {
pthread_mutex_lock(&g_mutex);
int a = g_num;
a++;
g_num = a;
printf("A thread id: %ld, num = %d\n", pthread_self(), g_num);
pthread_mutex_unlock(&g_mutex);
usleep(10); //沉睡 10 毫秒, 模擬時間片輪轉,效果更明顯
}
return nullptr;
}
void* funB(void* arg)
{
for (int i = 0; g_num < MAXNUMBER; i++) {
pthread_mutex_lock(&g_mutex);
int b = g_num;
b++;
g_num = b;
printf("B thread id: %ld, num = %d\n", pthread_self(), g_num);
pthread_mutex_unlock(&g_mutex);
usleep(10); //沉睡 10 毫秒, 模擬時間片輪轉,效果更明顯
}
return nullptr;
}
-
運行結果:
實際結果和預期結果一致。
下載地址:
歡迎 star 和 fork 這個系列的 linux 學習,附學習由淺入深的目錄。