Linux線程間同步 —— 互斥量(mutex)
1 互斥量簡介
互斥量是Linux線程間數據同步最主要和最常用的手段,能夠確保同一時間只有一個線程訪問數據。互斥量本質上就是一把鎖,當線程需要訪問數據時就啟用互斥量(加鎖),訪問完成后再釋放互斥量(解鎖)。對數據加鎖之后,其他線程不能訪問該數據,當然了也不能對該數據再次加鎖,這樣就保證任何時刻只有一個線程訪問共享數據。
如果線程嘗試對一個已經加鎖的數據再次加鎖,那么將會被阻塞,直到該數據解鎖為止。如果在解鎖時有一個以上的線程被阻塞,那么所有該鎖上的被阻塞線程都會變成可運行狀態,第一個變為運行的線程就可以對互斥量加鎖,其他的被阻塞線程將會繼續被阻塞只能繼續等待。這樣就保證了多個被阻塞的線程每次只有一個線程繼續運行。
只有將所有的線程都設計成按相同的規則加鎖解鎖才能保證加鎖的可靠性。所有的線程在訪問數據時都必須得到鎖才能訪問互斥量。如果允許其中一個線程在沒有得到鎖的情況下也可以訪問共享數據,那么其他的線程在使用共享數據之前都申請鎖,也還是會出現數據不一致的問題。
2 互斥量相關API函數簡介
下面對POSIX標準下的互斥量的標準API進行簡介。
2.1創建互斥量
互斥量是pthread_mutex_t類型的。互斥量既可以像靜態變量那樣分配,也可以在運行時動態創建(比如通過malloc()在一塊內存中分配)。對于靜態初始化把常量PTHREAD_MUTEX_INITIALIZER賦值給互斥量。
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
對于動態創建互斥量可以調用函數pthread_mutex_init()來完成。函數格式如下:
#include<pthread.h>
int pthread_mutex_init( pthread_mutex_t *restrict mutex,
const pthread _mutexattr_t *restrict attr);
參數說明:
mutex —— 創建的互斥量
attr —— 互斥量屬性,NULL代表互斥量的各種屬性取默認值。
返回值:初始化成功返回0;否則返回錯誤編號。
2.2 銷毀互斥量
當不需要自動或者動態分配的互斥量時,必須使用pthread_mutex_destroy()函數將其銷毀。函數格式如下:
#include<pthread.h>
int pthread_mutex_destroy( pthread_mutex_t * mutex );
參數說明:
mutex —— 需要銷毀的的互斥量
2.3 加鎖
采用函數pthread_mutex_lock()來對互斥量加鎖。如果互斥量已經加鎖,調用線程將阻塞直到互斥量解鎖。函數格式如下:
#include<pthread.h>
int pthread_mutex_lock( pthread_mutex_t * mutex );
參數說明:
mutex —— 需要加鎖的的互斥量
返回值:加鎖成功返回0;否則返回錯誤編號。
如果線程不希望被阻塞,可以調用pthread_mutex_trylock()來嘗試加鎖。如果調用pthread_mutex_trylock()函數時,互斥量處于未鎖住狀態,那么直接對互斥量加鎖,如果互斥量已經處于鎖住狀態,則pthread_mutex_trylock()就會失敗,不能對互斥量進行加鎖,返回EBUSY。函數具體格式如下:
#include<pthread.h>
int pthread_mutex_trylock( pthread_mutex_t * mutex );
參數說明:
mutex —— 需要嘗試加鎖的互斥量
返回值:加鎖成功返回0;否則返回EBUSY。
當線程試圖訪問一個已經加鎖的互斥量時,函數pthread_mutex_timedlock()允許設定線程阻塞時間。pthread_mutex_timedlock()函數與pthread_mutex_lock()函數基本上是等價的,但是在達到超時時間值時,pthread_mutex_timedlock()不會對互斥量進行加鎖,而是返回錯誤碼ETIMEOUT。函數原型如下:
#include<pthread.h>
int pthread_mutex_timedlock( pthread_mutex_t *restrict mutex,
const struct timespec *restrict tsptr );
參數說明:
mutex —— 需要加鎖的互斥量
tsptr —— 超時時間,線程愿意等待的絕對時間(與相對時間對比而言,指定在時間X之前可以等待,而不是愿意阻塞Y秒),用timespec結構來表示的(用秒和納秒來描述時間)。
返回值:加鎖成功返回0;否則返回EBUSY。
2.4 解鎖
如果線程對互斥量訪問結束,需要調用函數pthread_mutex_unlock()對互斥量進行解鎖。下列行為均屬于錯誤應該避免:對未加鎖的信號量解鎖,解鎖其他線程鎖定的互斥量。函數格式如下:
#include<pthread.h>
int pthread_mutex_unlock( pthread_mutex_t * mutex );
參數說明:
mutex —— 需要解鎖的互斥量
返回值:解鎖成功返回0;否則返回錯誤編號。
3 避免死鎖
互斥量使用不當就會形成死鎖,造成程序運行故障。通常情況下,以下情形會造成死鎖:
(1)線程試圖對同一個互斥量加鎖兩次,此線程將會陷入死鎖狀態;
(2)兩個線程都需要先獲得對方已經加鎖的互斥量才能繼續往前運行,那么這兩個線程就會互相阻塞,形成死鎖。
在設計編寫程序時,要避免死鎖情況的產生。常用的方法就是定義互斥量的層級關系。當多個線程對一組互斥量操作時,總以相同順序對該組互斥量進行鎖定,也就是仔細控制互斥量加鎖順序可以避免死鎖的產生。
有時候由于程序結構的限制,對互斥量進行排序很困難。這種情況下可以嘗試先釋放占有的鎖,然后過一段時間再試。可以使用pthread_mutex_trylock函數。如果占有鎖,使用pthread_mutex_trylock嘗試加新鎖成功,則程序繼續進行,如果失敗,可以先釋放已經占有的鎖,然后過一段時間再試。
4 例程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
//定義互斥量
pthread_mutex_t mtx_counter;
//定義互斥量屬性
pthread_mutexattr_t mtx_attr;
//定義全局變量
int counter;
void err_exit(const char *err_msg)
{
printf("\r\n error:%s! exit!\r\n",err_msg);
exit(1);
}
//創建線程
void *thread_fun(void *arg)
{
while(1)
{
//加鎖
pthread_mutex_lock( &mtx_counter );
printf(" Child thread , enter critical, counter = %d \n", counter);
if(counter == 10)
{
counter = 0;
printf("\r\n Operate is done, counter = %d \r\n",counter);
}
//解鎖
pthread_mutex_unlock( &mtx_counter );
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t tid;
//初始化互斥量
if(pthread_mutex_init(&mtx_counter, NULL) == -1)
{
err_exit("pthread_mutex_init()");
}
//創建一個線程
if(pthread_create(&tid, NULL, thread_fun, NULL) == -1)
{
err_exit("pthread_create()");
}
counter = 0;
while(1)
{
//加鎖
pthread_mutex_lock( &mtx_counter );
if(counter < 10)
{
counter = counter + 1;
printf(" mainthread, counter = %d \n",counter);
}
//解鎖
pthread_mutex_unlock( &mtx_counter );
sleep(1);
}
return 0;
}
程序有兩個線程。主線程對全局變量counter加1,子線程檢查counter是否加到10,如果加到10則把counter賦值為0。兩個線程在訪問全局變量counter時都加了鎖對臨界區進行了保護。程序運行結果如圖所示。
智能推薦
多核多線程自旋鎖spinlock 與互斥量mutex性能分析
多核多線程 自旋鎖(spinlock )與 互斥量(mutex) mutex方式:(sleep-wait) 從實現原理上來講,Mutex屬于sleep-waiting類型的鎖。例如在一個雙核的機器上有兩個線程(線程A和線程B),它們分別運行在Core0和Core1上。假設線程A想要通過pthread_mutex_lock操作去得到一個臨界區的鎖,而此時這個鎖正被線版程B所持有,那么線程A就會被阻塞...
14C++11多線程編程之Windows臨界區和各種互斥量(recursive_mutex,std::timed_mutex,recursive_timed_mutex)
1 Windows臨界區 1)Windows臨界區與C++11的mutex的區別:mutex不能多次lock,而Windows的臨界區可以多次進入,即EnterCriticalSection,但是進入多少次臨界區就需要多次LeaveCriticalSection離開臨界區。若少解鎖即多上鎖,它不會報錯,但是它的現象的只會在該線程工作。看下圖,當我inMsg少解鎖了一篇,只在inMsg中插入。 2)...
C++面試寶典:線程同步的方式(臨界區critical section,互斥量mutex&lock)
線程同步 1、臨界區 臨界區(Critical Section)是一段獨占對某些共享資源訪問的代碼,在任意時刻只允許一個線程對共享資源進行訪問。如果有多個線程試圖同時訪問臨界區,那么在有一個線程進入后其他所有試圖訪問此臨界區的線程將被掛起,并一直持續到進入臨界區的線程離開。臨界區在被釋放后,其他線程可以繼續搶占,并以此達到用原子方式操作共享資源的目的。 臨界區在使用時以CRITICAL_SECTI...
互斥量mutex
互斥量是將資源互斥,即進程在某一相同的程序時,可以將某一程序片段設定為互斥量,線程不能同時訪問此代碼段,只有當前線程退出此互斥鎖,另一線程才可以進入 上邊的程序是沒有加互斥鎖的程序,如以前,此程序會出現零票 現在我們對售票階段加上互斥鎖,即當前線程A在出票的時候線程B不讓出票,一次來達到互斥操作,雖然說賣票時不能同時操作,但是由于線程之間切換非常快,所以表現的還是多線程在同時操作 &...
互斥量 mutex
互斥量 mutex(臨界資源) 大部分情況,線程使用的數據都是局部變量,變量的地址空間在線程棧空間內,這種情況,其他線程無法獲得這種變量。 但有時候,很多變量都需要在線程間共享,這樣的變量稱為共享變量,可以通過數據的共享,完成線程之間的交互。 多個線程并發的操作共享變量,會帶來一些問題。 我們為了解決帶來的一系列問題,我們需要一把鎖。Linux上提供的這把鎖叫做互斥量。 讓我們用一張圖片來深入體會...
猜你喜歡
mutex互斥量(鎖)
來看個 購票的 代碼 在 sell函數中 執行到if 條件為真后,開始sleep,這時其他線程可能進入,產生讀臟數據。 另外 自增 自減操作本身并不是原子操作, 而是對應三條匯編指令 load 將共享變量 加載到 寄存器 update 更新寄存器的值 執行 + 1 / -1 操作 store 將新值從寄存器更新到共享變量的內存地址當中 解決產生讀臟數據的思路就是 保證一個線程進入臨界區后,其他線程...
線程間的同步與互斥—信號量
線程間的同步與互斥—信號量 之前的博客有學習過關于進程間通信的信號量,那關于線程之間的同步和互斥的信號量是怎么理解的呢? 線程之間的Mutex變量是非0即1的,可以看作是一種資源的計數器,初始化時Mutex是1,表示有一個可用資源,加鎖的時候表示獲得該資源,將Mutex減1,置為0,表示此時沒有可用資源,解鎖的時候在重新釋放該資源,將Mutex重新加到1,表示此時又有了一個資源。關于信...
線程間同步互斥(4)信號量使用
接下來。來看一看信號量的使用。 概念部分: 信號量廣泛用于進程或線程間的同步和互斥,信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。 編程時可根據操作信號量值的結果判斷是否對公共資源具有訪問的權限,當信號量值大于 0 時,則可以訪問,否則將阻塞。PV 原語是對信號量的操作,一次 P 操作使信號量減1,一次 V 操作使信號量加1。 函數接口部分: 信號量使用: 1、信號量實現線程互...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...