Linux 互斥鎖 遞迴鎖 自旋鎖 讀寫鎖

2021-10-24 08:30:36 字數 3895 閱讀 3925

在多執行緒中,我們經常會要用到鎖,那麼,鎖是什麼,我們為什麼要用到鎖?回到問題的本質,我們在什麼場景下會用到鎖?鎖是針對程式中的臨界資源,也就是公共資源的,當我們有兩個或多個執行緒同時對乙個臨界資源操作的時候,為了保證共享資料操作的完整性,我們要為這些公共資源加鎖。

在linux中常見的鎖主要有互斥鎖、自旋鎖、讀寫鎖,至於遞迴鎖則是互斥鎖的乙個特例。

在講什麼是互斥鎖之前,我們先來看一下下面這段**:

#includ #includ define thread_num 10

void *thread_proc(void *arg)

}int main()

; int count = 0;

​ int i = 0;

for(i = 0; i < thread_num; i ++)

// 每隔一秒列印一次count的值

for(i = 0; i < 100; i ++)

return 0;

}

他的執行結果為下面這樣: 

執行這段**,我們本意是想最後列印出來的count值能到100萬,但是實際上,最後我們列印出來的count值只會有99萬多,那這是為什麼呢?這就是由於多執行緒對同乙個臨界資源進行操作,我們**是只有一行idx++,但是在這行**翻譯成彙編**的時候就變成了這樣:

我們的一行idx++,翻譯成彙編**變成了3行。理想狀態下,我們希望者行彙編**能像下面這樣順序執行:

但是實際時,由於多執行緒的併發操作,使得部分時候執行的順序變成了這樣:

這就導致了我們上面的**會出現最後的列印只有99萬多的結果。為了確保不發生這種情況,我們就需要在對臨界資源操作時加上鎖。

linux中的互斥鎖是我們最常使用於執行緒同步的鎖,標記用來保證在任一時刻,只能有乙個執行緒訪問該物件,同一執行緒多次加鎖操作會造成死鎖,通常情況下鎖操作失敗會將該執行緒睡眠等待鎖釋放時被喚醒。那麼我們怎麼使用互斥鎖呢?pthread.h標頭檔案中就提供了互斥鎖的使用。

上面這些函式成功時返回0,失敗則返回錯誤碼。我們來在之前的**中加入鎖,再列印下結果:

#includ #includ define thread_num 10

pthread_mutex_t mutex;

void *thread_proc(void *arg)

}int main()

; int count = 0;

// 初始化互斥鎖

pthread_mutex_init(&mutex, null);

​ int i = 0;

for(i = 0; i < thread_num; i ++)

// 每隔一秒列印一次count的值

for(i = 0; i < 100; i ++)

return 0;

}

這裡可以看到,可以達到我們想要的結果100萬。

嚴格上講遞迴鎖只是互斥鎖的乙個特例,同樣只能有乙個執行緒訪問該物件,但遞迴鎖允許同乙個執行緒在未釋放其擁有的鎖時反覆對該鎖進行加鎖操作。windows下的臨界區預設是支援遞迴鎖的,而linux下的互斥量則需要設定引數pthread_mutex_recursive,預設則是不支援的。我們先來看下下面的**:

#include #include ​

int count = 0;

​pthread_mutex_t mutex;

​void* thread_proc(void*)}​

int main()

這裡,我們在互斥鎖lock後又呼叫了一次lock,這時,程式就會死鎖,不輸出任何資訊。

但是,如果我們這裡使用的是遞迴鎖的話,就不會有死鎖的問題。

#include #include ​

int count = 0;

​pthread_mutex_t mutex;

​void* thread_proc(void*)}​

int main()

使用遞迴鎖,結果就可以正確的輸出1~10000。

同樣用來標記只能有乙個執行緒訪問該物件,在同一執行緒多次加鎖操作會造成死鎖。使用硬體提供的swap指令或test_and_set指令實現,同互斥鎖不同的是在鎖操作需要等待的時候並不是睡眠等待喚醒,而是迴圈檢測保持者已經釋放了鎖。這樣做的好處是節省了執行緒從睡眠狀態到喚醒之間核心會產生的消耗,在加鎖時間短暫的環境下這點會提高很大效率。

自旋鎖的實現是為了保護一段短小的臨界區操作**,主要是用於在smp上保護臨界區,保證這個臨界區的操作是原子的,從而避免併發的競爭冒險。在linux核心中,自旋鎖通常用於包含核心資料結構的操作,你可以看到在許多核心資料結構中都嵌入有spinlock,這些大部分就是用於保證它自身被操作的原子性,在操作這樣的結構體時都經歷這樣的過程:上鎖-操作-解鎖。如果核心控制路徑發現自旋鎖「開著」(可以獲取),就獲取鎖並繼續自己的執行。相反,如果核心控制路徑發現鎖由執行在另乙個cpu上的核心控制路徑「鎖著」,就在原地「旋轉」,反覆執行一條緊湊的迴圈檢測指令,直到鎖被釋放。 自旋鎖是迴圈檢測「忙等」,即等待時核心無事可做(除了浪費時間),程序在cpu上保持執行,所以它保護的臨界區必須小,且操作過程必須短。不過,自旋鎖通常非常方便,因為很多核心資源只鎖1毫秒的時間片段,所以等待自旋鎖的釋放不會消耗太多cpu的時間。

自旋鎖的初始化有兩種方式:

自旋鎖的加鎖和解鎖:

在使用方法上,自旋鎖和互斥鎖差不多,這裡還用上面互斥鎖的那個例子:

#includ #includ define thread_num 10

pthread_spinlock_t spinlock;

void *thread_proc(void *arg)

}int main()

; int count = 0;

// 初始化自旋鎖

pthread_spin_init(&spinlock, pthread_process_shared);

​ int i = 0;

for(i = 0; i < thread_num; i ++)

// 每隔一秒列印一次count的值

for(i = 0; i < 100; i ++)

return 0;

}

互斥鎖用於臨界區持鎖時間比較長的操作,比如下面這些情況都可以考慮

至於自旋鎖就主要用在臨界區持鎖時間非常短且cpu資源不緊張的情況下,自旋鎖一般用於多核的伺服器。

讀寫鎖中的讀操作可以共享,寫操作是排它的,讀可以有多個在讀,寫只有唯一個在寫,寫的時候不允許讀操作。對於讀資料較修改資料頻繁的應用,用讀寫鎖代替互斥鎖可以提高效率。因為使用互斥鎖時,即使是讀出資料(相當於操作臨界區資源)都需要上互斥鎖;而採用讀寫鎖則允許在任一時刻多個讀出。

讀寫鎖的初始化有兩種方式:

讀寫鎖的加鎖和解鎖:

獲取讀寫鎖的讀操作有兩種方式:

如果對應的讀寫鎖被其它寫者持有,或者讀寫鎖被讀者持有,該執行緒都會阻塞等待。

網路程式設計 自旋鎖 互斥鎖 讀寫鎖 遞迴鎖

自旋鎖 互斥鎖 讀寫鎖 遞迴鎖 自旋鎖 互斥鎖 1.自旋鎖不會睡眠,互斥鎖會睡眠,因此自旋鎖效率高於互斥鎖。自旋鎖比較適用於鎖使用者保持鎖時間比較短的情況 2.自旋鎖消耗cpu 由於一直查詢,所以自旋鎖一直占用cpu,互斥鎖不會,自旋鎖導致cpu使用效率低 3.自旋鎖容易造成死鎖 比如遞迴呼叫 遞迴...

互斥鎖 遞迴鎖 讀寫鎖和自旋鎖區別

共享資源的使用是互斥的,即乙個執行緒獲得資源的使用權後就會將改資源加鎖,使用完後會將其解鎖,所以在使用過程中有其它執行緒想要獲取該資源的鎖,那麼它就會被阻塞陷入睡眠狀態,直到該資源被解鎖才會別喚醒,如果被阻塞的資源不止乙個,那麼它們都會被喚醒,但是獲得資源使用權的是第乙個被喚醒的執行緒,其它執行緒又...

自旋鎖和互斥鎖

1.理論分析 從理論上說,如果乙個執行緒嘗試加鎖乙個互斥鎖的時候沒有成功,因為互斥鎖已經被鎖住了,這個未獲取鎖的執行緒會休眠以使得其它執行緒可以馬上執行。這個執行緒會一直休眠,直到持有鎖的執行緒釋放了互斥鎖,休眠的執行緒才會被喚醒。如果乙個執行緒嘗試獲得乙個自旋鎖的時候沒有成功,該執行緒會一直嘗試加...