一、互斥量mutex
- Linux提供一把互斥鎖mutex(也稱之為互斥量)
- 每個線程在對資源操作前都嘗試先加鎖,成功加鎖才能操作,操作結束后解鎖。
- 資源還是共享的,線程間也還是競爭的,但通過鎖將資源的訪問變為互斥操作,而后與時間有關的錯誤也不會在產生了。
如圖所示:
但是應該注意:同一個時刻,只能有一個線程持有該鎖。
當A線程對某個全局變量加鎖訪問,B在訪問前嘗試加鎖,拿不到鎖,B阻塞。C線程不去加鎖,而直接訪問該全局變量,依然能夠訪問,但會出現數據混亂。
所以,互斥鎖實質上是是操作系統提供的一把“建議鎖”(又稱“協同所”),建議程序中有多線程訪問共享資源的時候使用該機制,但是,并沒有強制限定。
?
二、主要應用函數
分析:
- pthread_mutex_t 類型,其本質是一個結構體,為簡化理解,應用時可忽略其實現細節,簡單當成整數看待。
- pthread_mutex_t? mutex:變量mutex只有兩種取值0、1;
1. 函數原型:
功能:初始化一個互斥鎖(互斥量);?初值可看做1
#include<pthread.h>
pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 返回值:若成功,返回0,否則,返回錯誤編號
參數1:傳出參數,調用時應傳&mutex
- restrict關鍵字:只用于限制指針,告訴編譯器,所有修改該指針指向內存中內容的操作,只能通過本指針完成。不能通過除本指針以外的其他變量或指針修改。
參數2:互斥屬性。是一個傳入參數,通常傳NULL,選用默認屬性(線程間共享).
- 靜態初始化:如果互斥鎖mutex是靜態分配的(定義在全局,或加了static關鍵字修飾),可以直接使用宏進行初始化。pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- 動態初始化:局部變量應采用動態初始化。pthread_mutex_init(&mutex, NULL);
?
2. 函數原型:
? ? 功能:銷毀一個互斥鎖
#include<pthread.h>
pthread_mutex_destroy(pthread_mutex_t *mutex); 返回值:若成功,返回0,否則,返回錯誤編號
?
3. 函數原型:
? ? 功能:加鎖。可理解為將mutex--(或-1)
#include<pthread.h>
pthread_mutex_lock(pthread_mutex_t *mutex);返回值:若成功,返回0,否則,返回錯誤編號
分析:
- 沒有被上鎖,當前線程會將這把鎖鎖上
- 被鎖上了:當前線程阻塞,鎖被打開之后,線程解除阻塞。
?
4. 函數原型:
? ?功能:解鎖。可理解為將mtex++(或+1)
#include<pthread.h>
pthread_mutex_unlock(pthread_mutex_t *mutex);返回值:若成功,返回0,否則,返回錯誤編號
注意:
- 同時將阻塞在該鎖上的所有線程全部喚醒
?
5. 函數原型:
功能:嘗試加鎖, 失敗返回, 不阻塞
#include<pthread.h>
pthread_mutex_trylock(pthread_mutex_t *mutex);返回值:若成功,返回0,否則,返回錯誤編號
分析:
- 沒有鎖上:當前線程會給這把鎖加鎖
- 如果鎖上了:不會阻塞,返回
?
三、程序清單(一)
1. 測試代碼:
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>void *tfn(void *arg)
{srand(time(NULL));while(1) {printf("hello ");sleep(rand() % 3); //模擬長時間操作共享資源,導致cpu易主,產生與時間有關的錯誤printf("word\n");sleep(rand() % 3);}return NULL;
}int main()
{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);}return 0;
}
運行結果:
?
2. 測試代碼:
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>pthread_mutex_t mutex;void *tfn(void *arg)
{srand(time(NULL));while(1) {pthread_mutex_lock(&mutex);printf("hello ");sleep(rand() % 3); //模擬長時間共享資源,導致cpu易主。產生于時間有關的錯誤 printf("word\n");pthread_mutex_unlock(&mutex);sleep(rand() % 3);}return NULL;
}int main()
{pthread_t tid;srand(time(NULL));pthread_mutex_init(&mutex, NULL);pthread_create(&tid, NULL, tfn, NULL); //mutex == 1while(1) {pthread_mutex_lock(&mutex);printf("HELLO ");sleep(rand() % 3);printf("WORLD\n");pthread_mutex_unlock(&mutex);sleep(rand() % 3);}pthread_mutex_destroy(&mutex);return 0;
}
輸出結果:
?
?
3. 測試代碼:
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>pthread_mutex_t mutex;void *tfn(void *arg)
{srand(time(NULL));while(1) {pthread_mutex_lock(&mutex);printf("hello ");sleep(rand() % 3); //模擬長時間共享資源,導致cpu易 主。產生于時間有關的錯誤 printf("word\n");sleep(rand() % 3);pthread_mutex_unlock(&mutex);}return NULL;
}int main()
{pthread_t tid;srand(time(NULL));pthread_mutex_init(&mutex, NULL);pthread_create(&tid, NULL, tfn, NULL); //mutex == 1while(1) {pthread_mutex_lock(&mutex);printf("HELLO ");sleep(rand() % 3);printf("WORLD\n"); sleep(rand() % 3);pthread_mutex_unlock(&mutex);}pthread_mutex_destroy(&mutex);return 0;
}
輸出結果:
四、程序清單(二)
四、加鎖與解鎖
lock與unlock:
- lock嘗試加鎖,如果加鎖不成功,線程阻塞,阻塞到持有該互斥量的其他線程解鎖為止。
- unlock主動解鎖,同時將阻塞到該鎖上所有線程全部喚醒,至于哪個線程先被喚醒取決于優先級,調度。默認:先阻塞、先喚醒。
例如:T1 T2 T3 T4 使用一把mutex鎖,T1加鎖成功,其他線程均阻塞,直至T1解鎖,T1解鎖后,T2 T3 T4均被喚醒,并自動再次嘗試加鎖。
可假想mutex鎖init成功初值為1。lock功能是將mutex--。unlock將mutex++。
?
lock與trylock:
- lock加鎖失敗會阻塞,等待鎖釋放。
- trylock加鎖失敗直接返回錯誤號(如EBUSY),不阻塞。
?
注意:在訪問共享資源前加鎖,訪問結束后立即解鎖。鎖的“粒度”越小越好。