Linux 執行緒互斥

2021-09-25 18:59:56 字數 3498 閱讀 2648

在之前的部落格中,我講到執行緒的相關概念和執行緒的控制,在本節中我們聊一下執行緒互斥。

五個概念

多執行緒併發的問題

然而多執行緒併發的操作共享變數會帶來一些問題:

先看下面**

#include #include #include using namespace std;

int goal=0;

void* handle(void* arg)

int main()

{ pthread_t t1,t2;

pthread_create(&t1,null,handle,null);

pthread_create(&t2,null,handle,null);

sleep(1);

cout不看不知道,一看嚇一跳!!本來我以為我寫了兩個執行緒分別對goal加10000000次,最後的結果應該是20000000,然而!!

通過執行結果,我發現執行了三次的結果都不相同,而且也沒有正確的結果。那麼問題出在**呢?

原來是因為兩個執行緒是併發執行的,不同的執行緒在執行時有不同的暫存器,他們可能同時取走了某一時刻的goal,對它分別進行++後結果都加了一,但最後寫回記憶體時由原來的加2變成只加1,導致結果越錯越離譜。這裡的根本原因是++這個操作並不是原子性的。看下圖你應該就能明白了

來看我們在vs2013下一段簡單的反彙編**:從圖中你可以清楚的看到對於goal的++操作分解成了三步,所以這就是導致上面問題的罪魁禍首

如果++是乙個原子性的操作的話,那麼就不會出現上述的問題,所以現在要想解決上面的問題要麼將上述**實現原子性操作,要麼實現互斥的機制,linux中確實提供了原子的++操作(atomic)但是這僅僅解決了當前問題,所以我們這裡重點介紹執行緒的互斥鎖實現互斥。

互斥量mutex

為了解決上面的問題,我們需要做到3點:

**必須要有互斥行為:當**進入臨界區執行時,不允許其他執行緒進入該臨界區

如果多個執行緒同時要執行臨界區的**,並且沒有執行緒在執行,那麼只允許乙個執行緒進入該臨界區

如果執行緒不在臨界區中執行,那麼該執行緒不能阻止其他執行緒進入臨界區

要做到以上三點,本質上需要一把鎖,linux下提供了一把鎖叫做互斥量:

接下來我介紹一下互斥量的介面。

互斥量的介面

初始化互斥量:有兩種方法

靜態分配

pthread_mutex_t mutex = pthread_mutex_initializer

動態分配

int pthread_mutex_init(pthread_mutex_t restrict mutex, const pthread_mutexattr_trestrict attr);

//引數1為要初始化的互斥鎖,引數2一般設定為null表示使用預設屬性

銷毀互斥量:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

注意:使用pthread_mutex_initializer初始化的互斥量不需要銷毀

不要銷毀乙個被加鎖的互斥量

已經銷毀的互斥量不能再被執行緒加鎖

互斥量加鎖和解鎖:

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

注意:呼叫pthread_lock時,可能會遇到以下情況:

互斥量處於未鎖狀態,該函式會將互斥量鎖定,同時返回成功

發起函式呼叫時,其他執行緒已經鎖定互斥量,或者存在其他執行緒同時申請互斥量,但沒有競爭到互斥量,那麼pthread_ lock呼叫會陷入阻塞(執行流被掛起),等待互斥量解鎖

下面我們使用上面的函式,修改之前的**為執行緒安全的:

這時如果當前沒有執行緒在臨界區中,那麼執行緒一訪問臨界資源的時候會先進行加鎖處理,當執行緒二要訪問臨界資源時發現資源被鎖,所以os將執行緒二設定為非r狀態並將其加入到阻塞佇列中等待執行緒一釋放鎖資源。這樣就可以保證執行緒在對臨界區訪問時任一時刻只有乙個執行緒,也就是說我們現在可以認為我們的程式++是原子的。

執行結果如下:

互斥量實現的原理

互斥鎖的實現原理實際上非常的簡單,我之前說到過,當彙編是一句指令時就可以認為是原子的,所以為了實現鎖的互斥操作,大多數體系結構對於互斥鎖的實現使用了swap或者exchange指令,該指令的作用是把暫存器和單元資料交換,因為就算是多執行緒,匯流排週期也有先後時間,所以利用此指令總能保證同乙個時間只有乙個執行緒進入臨界區。

現在我們來看看如何使用swap或者exchange指令實現互斥鎖原理,來看下面的一段偽**:可以看到鎖資源最開始拿到的值為1,然後使用swap或exchange指令將暫存器中的值和鎖擁有的值交換,如果暫存器中現在值為1,那麼表示資源申請成功,如果暫存器中值為0,說明鎖資源中的1已經被其他執行緒交換走了,所以當前程序也就需要進入等待佇列。

注意:加鎖操作要求原子性,解鎖操作無要求。

執行緒安全

執行緒安全:多個執行緒併發處理同一段**時,不會出現不同的結果。常見的對全域性變數或者靜態變數進行操作並且沒有鎖的保護下,會出現不同的結果。

重入:同一函式被不同的執行流呼叫,當前執行流未執行完畢就有其他執行流再次進入,稱這種現象為重入。乙個函式在重入情況下,執行結果不會出現任何問題,稱為可重入函式,否則稱為不可重入函式。

常見執行緒不安全的情況:

不保護共享變數的函式

函式的狀態隨著被呼叫,函式發生變化的函式

返回指向靜態變數指標的函式

呼叫執行緒不安全函式的函式

常見執行緒安全的情況:

每個執行緒對於全域性變數或者靜態變數只有讀取的許可權,而沒有寫入的許可權

類或者介面對於執行緒來說都是原子操作

多個執行緒之間的切換不會導致該介面的執行結果存在二義性

可重入與執行緒安全

Linux 多執行緒互斥量互斥

同乙個程序中的多個執行緒共享所在程序的記憶體資源,當多個執行緒在同一時刻同時訪問同一種共享資源時,需要相互協調,以避免出現資料的不一致和覆蓋等問題,執行緒之間的協調和通訊的就叫做執行緒的同步問題,執行緒同步的思路 讓多個執行緒依次訪問共享資源,而不是並行 mutex被建立時可以有初始值,表示mute...

linux執行緒互斥鎖

使用執行緒編寫程式需要技巧,而多執行緒的程式中的bug非常難以跟蹤 除錯,因為這些bug經常是難以再現的。競爭條件 當乙個執行緒訪問乙個資料結構的時候,另乙個執行緒也訪問同乙個資料結構,這時就出現了競爭條件 兩個執行緒 也可能是多個 競爭對同乙個資源的訪問。當其中乙個執行緒處理到一部分的時候,另外的...

linux 執行緒互斥鎖

一,鎖的建立 鎖可以被動態或靜態建立,可以用巨集pthread mutex initializer來靜態的初始化鎖,採用這種方式比較容易理解,互斥鎖是pthread mutex t的結構體,而這個巨集是乙個結構常量,如下可以完成靜態的初始化鎖 pthread mutex t mutex pthrea...