Linux中提供一把互斥鎖mutex(也稱之為互斥量)。
每個線程在對資源操作前都嘗試先加鎖,成功加鎖才能操作,操作結(jié)束解鎖。
資源還是共享的,線程間也還是競爭的,但通過“鎖”就將資源的訪問變成互斥操作,而后與時間有關(guān)的錯誤也不會再產(chǎn)生了。

但,應(yīng)注意:同一時刻,只能有一個線程持有該鎖。
當(dāng)A線程對某個全局變量加鎖訪問,B在訪問前嘗試加鎖,拿不到鎖,B阻塞。C線程不去加鎖,而直接訪問該全局變量,依然能夠訪問,但會出現(xiàn)數(shù)據(jù)混亂。
所以,互斥鎖實質(zhì)上是操作系統(tǒng)提供的一把“建議鎖”(又稱“協(xié)同鎖”),建議程序中有多線程訪問共享資源的時候使用該機制。但,并沒有強制限定。
因此,即使有了mutex,如果有線程不按規(guī)則來訪問數(shù)據(jù),依然會造成數(shù)據(jù)混亂。
主要應(yīng)用函數(shù):
pthread_mutex_init
pthread_mutex_destroy
pthread_mutex_lock
pthread_mutex_trylock
pthread_mutex_unlock
以上5個函數(shù)的返回值都是:成功返回0, 失敗返回錯誤號。
pthread_mutex_t 類型,其本質(zhì)是一個結(jié)構(gòu)體。為簡化理解,應(yīng)用時可忽略其實現(xiàn)細(xì)節(jié),簡單當(dāng)成整數(shù)看待。
pthread_mutex_t mutex; 變量mutex只有兩種取值1、0。
pthread_mutex_init函數(shù)
初始化一個互斥鎖(互斥量) ---> 初值可看作1
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
參1:傳出參數(shù),調(diào)用時應(yīng)傳 &mutex
restrict關(guān)鍵字:只用于限制指針,告訴編譯器,所有修改該指針指向內(nèi)存中內(nèi)容的操作,只能通過本指針完成。不能通過除本指針以外的其他變量或指針修改
參2:互斥量屬性。是一個傳入?yún)?shù),通常傳NULL,選用默認(rèn)屬性(線程間共享)。 參APUE.12.4同步屬性
pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
- 靜態(tài)初始化:如果互斥鎖 mutex 是靜態(tài)分配的(定義在全局,或加了static關(guān)鍵字修飾),可以直接使用宏進行初始化。
pthread_mutex_init(&mutex, NULL)
- 動態(tài)初始化:局部變量應(yīng)采用動態(tài)初始化。
pthread_mutex_destroy函數(shù)
銷毀一個互斥鎖
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_lock函數(shù)
加鎖??衫斫鉃閷utex--(或-1)
int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_unlock函數(shù)
解鎖??衫斫鉃閷utex ++(或+1)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_trylock函數(shù)
嘗試加鎖
int pthread_mutex_trylock(pthread_mutex_t *mutex);
加鎖與解鎖
lock與unlock:
lock嘗試加鎖,如果加鎖不成功,線程阻塞,阻塞到持有該互斥量的其他線程解鎖為止。
unlock主動解鎖函數(shù),同時將阻塞在該鎖上的所有線程全部喚醒,至于哪個線程先被喚醒,取決于優(yōu)先級、調(diào)度。默認(rèn):先阻塞、先喚醒。
例如:T1 T2 T3 T4 使用一把mutex鎖。T1加鎖成功,其他線程均阻塞,直至T1解鎖。T1解鎖后,T2 T3 T4均被喚醒,并自動再次嘗試加鎖。
可假想mutex鎖 init成功初值為1。 lock 功能是將mutex--。 unlock將mutex++
lock與trylock:
lock加鎖失敗會阻塞,等待鎖釋放。
trylock加鎖失敗直接返回錯誤號(如:EBUSY),不阻塞。
加鎖步驟測試:
看如下程序:該程序是非常典型的,由于共享、競爭而沒有加任何同步機制,導(dǎo)致產(chǎn)生于時間有關(guān)的錯誤,造成數(shù)據(jù)混亂:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *tfn(void *arg)
{
srand(time(NULL));
while (1)
{
printf("hello ");
sleep(rand() % 3); /*模擬長時間操作共享資源,導(dǎo)致cpu易主,產(chǎn)生與時間有關(guān)的錯誤*/
printf("world\n");
sleep(rand() % 3);
}
return NULL;
}
int main(void)
{
pthread_t tid;
srand(time(NULL));
pthread_create(&tid, NULL, tfn, NULL);
while (1)
{
printf("HELLO ");
sleep(rand() % 3);
printf("WORLD\n");
sleep(rand() % 3);
}
pthread_join(tid, NULL);
return 0;
}
- 定義全局互斥量,初始化init(&m, NULL)互斥量,添加對應(yīng)的destry
- 兩個線程while中,兩次printf前后,分別加lock和unlock
- 將unlock挪至第二個sleep后,發(fā)現(xiàn)交替現(xiàn)象很難出現(xiàn)。
線程在操作完共享資源后本應(yīng)該立即解鎖,但修改后,線程抱著鎖睡眠。睡醒解鎖后又立即加鎖,這兩個庫函數(shù)本身不會阻塞。
所以在這兩行代碼之間失去cpu的概率很小。因此,另外一個線程很難得到加鎖的機會。 - main 中加flag = 5 將flg在while中-- 這時,主線程輸出5次后試圖銷毀鎖,但子線程未將鎖釋放,無法完成。
- main 中加pthread_cancel()將子線程取消。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int sum=0;
void *thr1(void *arg)
{
while(1)
{
//先上鎖
pthread_mutex_lock(&mutex);//加鎖當(dāng)有線程已經(jīng)加鎖的時候會阻塞
printf("hello");
sleep(rand()%3);
printf("world\n");
//釋放鎖
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
}
void *thr2(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
printf("HELLO");
sleep(rand()%3);
printf("WORLD\n");
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
}
int main()
{
pthread_t tid[2];
pthread_create(&tid[0],NULL,thr1,NULL);
pthread_create(&tid[1],NULL,thr2,NULL);
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
return 0;
}
結(jié)論:
在訪問共享資源前加鎖,訪問結(jié)束后立即解鎖。鎖的“粒度”應(yīng)越小越好。