在多個線程對共享數據進行處理時,可能會發生衝突從而導致數據結果與預期結果不一樣,導致程序運行錯誤。
例:在一個主線程中有兩個線程,這兩個線程都對全局變量num進行+1操作。
注意:上述說的例子可能不會發生,只有當線程1還未將num的新值寫入內存時突然被切換到線程2纔可能發生。
線程間的切換會在系統由內核態切換到用戶態時可能發生。系統在運行系統調用時會進入內核態,運行用戶代碼時,會進入用戶態。
實例代碼:對全局num=0,線程1加5000次,線程2加5000次
#include<stdio.h>
#include<pthread.h>
int num = 0;
//這個函數對num+1,打印,進行5000次
void *Add(void *arg)
{
int i = 0;
while(i < 5000)
{
int tmp = num + 1;
//printf其實是對系統調用接口的封裝,在這裏系統會切換爲內核態,在切換時就有可能發生線程的切換
printf("%d\n", tmp);
num = tmp;
i++;
}
}
int main()
{
//定義兩個線程
pthread_t thread1;
pthread_t thread2;
//創建兩個線程 ,兩個線程都去執行Add函數
pthread_create(&thread1, NULL, Add, NULL);
pthread_create(&thread2, NULL, Add, NULL);
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
return 0;
}
運行結果:
運行結果一直爲6000多或者7000,從來沒有一次運行正確,可以自行驗證。
總結:
在多線程程序中,對於共享數據的處理要格外的小心,一定要保證一個線程對共享數據進行處理時其他線程不能對數據進行處理,也就是說對於能處理同一個共享數據的線程來說,他們對共享數據的訪問和處理必須是互斥的。這樣就能保證運行結果的正確性。爲了能讓線程之間發生互斥,我們可以使用互斥所鎖來實現。獲得鎖的線程可以完成“讀-修改-寫”的操作,然後釋放鎖給其它線程,沒有獲得鎖的線程只能等待而不能訪問共享數據,這樣“讀-修改-寫”三步操作組成一個原子操作,要麼都執行,要麼都不執行,不會執行到中間被打斷,也不會在其它處理器上並行做這個操作。
互斥鎖的使用:
互斥鎖用int pthread_mutex_t類型的變量表示。
初始化:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
參數描述:
mutex:代表要初始化的互斥鎖
attr:爲設定鎖的屬性,創建鎖時默認爲NULL
返回值:
成功爲0,失敗爲錯誤碼
如果鎖是靜態分配的(全局變量或者static變量),可以使用宏定義PTHREAD_MUTEX_INITALIZE來初始化。
銷燬鎖:
int pthrea_mutex_destroy(pthread_mutex_t *mutex);
成功返回0,失敗返回錯誤碼。
加鎖:
int pthread_mutex_lock(pthread_mutex_t *mutex);
//成功返回0,運行出錯則返回錯誤碼,申請不到鎖會被掛起到等待隊列
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//嘗試加鎖,與上面的區別在於,這個申請鎖失敗,會直接退出
解鎖:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//成功返回0,運行出錯返回錯誤碼
解決上述問題:
代碼:
#include<stdio.h>
#include<pthread.h>
//創建一個互斥鎖
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int num = 0;
void *Add(void *arg)
{
int i = 0;
while(i < 5000)
{
//在進入臨界區前先申請鎖資源
pthread_mutex_lock(&lock);
int tmp = num + 1;
printf("%d\n", tmp);
num =tmp;
i++;
//在出臨界區之後釋放鎖資源
pthread_mutex_unlock(&lock);
}
}
int main()
{
//兩個線程
pthread_t thread1;
pthread_t thread2;
//創建兩個線程
pthread_create(&thread1, NULL, Add, NULL);
pthread_create(&thread2, NULL, Add, NULL);
//等待線程
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
//銷燬鎖
pthread_mutex_destroy(&lock);
return 0;
}
運行結果:
10000
互斥鎖的實現原理:
互斥鎖可以讓線程之間實現互斥,那麼它是怎樣實現的呢?
假設mutex變量的值爲1表示互斥鎖空,這時某個進程調用lock就可以獲得鎖,而mutex爲0表示互斥鎖已經被某個線程獲得,其他線程去進行lock操作時只能掛起等待。
注意:有可能有兩個線程同時去進行lock操作,這時mutex是1,兩個線程都判斷mutex>0成立,結果兩個線程都以爲自己獲得了鎖。所以對mutex變量的讀取、判斷和修改應該是原子性的操作。
下面是互斥鎖的僞代碼實現:
movb 和 xchgb這兩條指令都是原子性的,所以mutex的讀取、判斷和修改也都保證了其原子性。