1. 互斥鎖
互斥鎖用於控制多線程對他們之間共享資源互斥訪問的一個信號量。也就是說為了避免多個線程在某一時刻同時操作一個共享資源。例如線程池中的多個空閑線程和一個任務隊列。任何時刻一個線程都要使用互斥鎖互斥訪問任務隊列,以避免多個線程同時訪問任務隊列以發生錯亂。
在某一時刻,只有一個線程可以獲取互斥鎖,在釋放互斥鎖之前其他線程都不能獲取該互斥鎖。如果其他線程想要獲取這個互斥鎖,那么這個線程只能以阻塞方式進行等待
1 <pthread.h>
2 3 pthread_mutex_t, 4 5 pthread_mutex_init(pthread_mutex_t * mutex, const phtread_mutexattr_t * mutexattr);//動態方式創建鎖,相當於new動態創建一個對象 6 7 pthread_mutex_destory(pthread_mutex_t *mutex)//釋放互斥鎖,相當於delete 8 9 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//以靜態方式創建鎖 10 11 pthread_mutex_lock(pthread_mutex_t *mutex)//以阻塞方式運行的。如果之前mutex被加鎖了,那么程序會阻塞在這里。 12 13 pthread_mutex_unlock(pthread_mutex_t *mutex) 14 15 int pthread_mutex_trylock(pthread_mutex_t * mutex);//會嘗試對mutex加鎖。如果mutex之前已經被鎖定,返回非0,;如果mutex沒有被鎖定,則函數返回並鎖定mutex 16 //該函數是以非阻塞方式運行了。也就是說如果mutex之前已經被鎖定,函數會返回非0,程序繼續往下執行。
2. 條件鎖
條件鎖就是所謂的條件變量,某一線程因為某個條件為滿足時,可以使用條件變量使該程序處於阻塞狀態。一旦條件滿足以“信號量”方式喚醒一個因為該條件而被阻塞的線程。最為常見的就是在線程池中,起初沒有任務時,任務隊列為空,此時線程池中的線程因為“任務隊列為空”這個條件處於阻塞狀態。一旦有任務進來,就會以信號量的方式喚醒一個線程來處理這個任務。 這個過程中用到了條件變量pthread_conf_t。
1 <pthread.h>
2
3 pthread_cond_t 4
5 pthread_cond_init(pthread_cond_t * condtion, const phtread_condattr_t * condattr);//對條件變量進行動態初始化,相當於new創建對象
6
7 pthread_cond_destory(pthread_cond_t * condition);//釋放動態申請的條件變量,相當於delete釋放對象
8
9 pthread_cond_t condition = PTHREAD_COND_INITIALIZER;//靜態初始化條件變量
10
11 pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);//該函數以阻塞方式執行。如果某個線程中的程序執行了該函數,那么這個線程就會以阻塞方式等待,直到收到pthread_cond_signal或者pthread_cond_broadcast函數發來的信號而被喚醒。
12
13 注意:pthread_cond_wait函數的語義相當於:首先解鎖互斥鎖,然后以阻塞方式等待條件變量的信號,收到信號后又會對互斥鎖加鎖。 14
15 為了防止“虛假喚醒”,該函數一般放在while循環體中。例如 16
17
18
19
20 pthread_mutex_lock(mutex);//加互斥鎖
21
22 while(條件不成立)//當前線程中條件變量不成立
23
24 { 25
26 pthread_cond_wait(cond, mutex);//解鎖,其他線程使條件成立發送信號,加鎖。
27
28 } 29
30 ...//對進程之間的共享資源進行操作
31
32 pthread_mutex_unlock(mutex);//釋放互斥鎖
33
34 pthread_cond_signal(pthread_cond_t * cond);//在另外一個線程中改變線程,條件滿足發送信號。喚醒一個等待的線程(可能有多個線程處於阻塞狀態),喚醒哪個線程由具體的線程調度策略決定
35
36 pthread_cond_broadcast(pthread_cond_t * cond);//以廣播形式喚醒所有因為該條件變量而阻塞的所有線程,喚醒哪個線程由具體的線程調度策略決定
37
38 pthread_cond_timedwait(pthread_cond_t * cond, pthread_mutex_t * mutex, struct timespec * time);//以阻塞方式等待,如果時間time到了條件還沒有滿足還是會結束
3. 自旋鎖
前面的兩種鎖是比較常見的鎖,也比較容易理解。下面通過比較互斥鎖和自旋鎖原理的不同,這對於真正理解自旋鎖有很大幫助。
假設我們有一個兩個處理器core1和core2計算機,現在在這台計算機上運行的程序中有兩個線程:T1和T2分別在處理器core1和core2上運行,兩個線程之間共享着一個資源。
首先我們說明互斥鎖的工作原理,互斥鎖是是一種sleep-waiting的鎖。假設線程T1獲取互斥鎖並且正在core1上運行時,此時線程T2也想要獲取互斥鎖(pthread_mutex_lock),但是由於T1正在使用互斥鎖使得T2被阻塞。當T2處於阻塞狀態時,T2被放入到等待隊列中去,處理器core2會去處理其他任務而不必一直等待(忙等)。也就是說處理器不會因為線程阻塞而空閑着,它去處理其他事務去了。
而自旋鎖就不同了,自旋鎖是一種busy-waiting的鎖。也就是說,如果T1正在使用自旋鎖,而T2也去申請這個自旋鎖,此時T2肯定得不到這個自旋鎖。與互斥鎖相反的是,此時運行T2的處理器core2會一直不斷地循環檢查鎖是否可用(自旋鎖請求),直到獲取到這個自旋鎖為止。
從“自旋鎖”的名字也可以看出來,如果一個線程想要獲取一個被使用的自旋鎖,那么它會一致占用CPU請求這個自旋鎖使得CPU不能去做其他的事情,直到獲取這個鎖為止,這就是“自旋”的含義。
當發生阻塞時,互斥鎖可以讓CPU去處理其他的任務;而自旋鎖讓CPU一直不斷循環請求獲取這個鎖。通過兩個含義的對比可以我們知道“自旋鎖”是比較耗費CPU的。
<linux\spinlock.h> spinlock_t spin_lock_init(spinlock_t *x); //初始化
spin_lock(x); //只有在獲得鎖的情況下才返回,否則一直“自旋”
spin_is_locked(x) //該宏用於判斷自旋鎖x是否已經被某執行單元保持(即被鎖),如果是,返回真,否則返回假。
注意:自旋鎖適合於短時間的的輕量級的加鎖機制。
4. 讀寫鎖
說到讀寫鎖我們可以借助於“讀者-寫者”問題進行理解。首先我們簡單說下“讀者-寫者”問題。
計算機中某些數據被多個進程共享,對數據庫的操作有兩種:一種是讀操作,就是從數據庫中讀取數據不會修改數據庫中內容;另一種就是寫操作,寫操作會修改數據庫中存放的數據。因此可以得到我們允許在數據庫上同時執行多個“讀”操作,但是某一時刻只能在數據庫上有一個“寫”操作來更新數據。這就是一個簡單的讀者-寫者模型。
