linux下為了多線程同步,通常用到鎖的概念。
posix下抽象了一個鎖類型的結構:ptread_mutex_t。通過對該結構的操作,來判斷資源是否可以訪問。顧名思義,加鎖(lock)后,別人就無法打開,只有當鎖沒有關閉(unlock)的時候才能訪問資源。
即對象互斥鎖的概念,來保證共享數據操作的完整性。每個對象都對應於一個可稱為" 互斥鎖" 的標記,這個標記用來保證在任一時刻,只能有一個線程訪問該對象。
使用互斥鎖(互斥)可以使線程按順序執行。通常,互斥鎖通過確保一次只有一個線程執行代碼的臨界段來同步多個線程。互斥鎖還可以保護單線程代碼。
要更改缺省的互斥鎖屬性,可以對屬性對象進行聲明和初始化。通常,互斥鎖屬性會設置在應用程序開頭的某個位置,以便可以快速查找和輕松修改。
l 頭文件:
#include <pthread.h>
l 函數原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
l 函數作用:
該函數用於C函數的多線程編程中,互斥鎖的初始化。
pthread_mutex_init() 函數是以動態方式創建互斥鎖的,參數attr指定了新建互斥鎖的屬性。如果參數attr為空(NULL),則使用默認的互斥鎖屬性,默認屬性為快速互斥鎖 。互斥鎖的屬性在創建鎖的時候指定,在LinuxThreads實現中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。
pthread_mutexattr_init() 函數成功完成之后會返回零,其他任何返回值都表示出現了錯誤。
函數成功執行后,互斥鎖被初始化為未鎖住態。
l 互斥鎖pthread_mutex_t的使用:
1. 互斥鎖創建和銷毀
有兩種方法創建互斥鎖,靜態方式和動態方式。POSIX定義了一個宏PTHREAD_MUTEX_INITIALIZER來靜態初始化互斥鎖,方法如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
在LinuxThreads實現中,pthread_mutex_t是一個結構,而PTHREAD_MUTEX_INITIALIZER則是一個結構常量。
動態方式是采用pthread_mutex_init()函數來初始化互斥鎖,API定義如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
其中mutexattr用於指定互斥鎖屬性(見下),如果為NULL則使用缺省屬性。
pthread_mutex_destroy()用於注銷一個互斥鎖,API定義如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
銷毀一個互斥鎖即意味着釋放它所占用的資源,且要求鎖當前處於開放狀態。由於在Linux中,互斥鎖並不占用任何資源,因此LinuxThreads中的 pthread_mutex_destroy()除了檢查鎖狀態以外(鎖定狀態則返回EBUSY)沒有其他動作。
2.互斥鎖屬性
互斥鎖的屬性在創建鎖的時候指定,在LinuxThreads實現中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。當前(glibc2.2.3,linuxthreads0.9)有四個值可供選擇:
* PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當一個線程加鎖以后,其余請求鎖的線程將形成一個等待隊列,並在解鎖后按優先級獲得鎖。這種鎖策略保證了資源分配的公平性。
* PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,並通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競爭。
* PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同。這樣保證當不允許多次加鎖時不出現最簡單情況下的死鎖。
* PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖類型,僅等待解鎖后重新競爭。
3.其他鎖操作
鎖操作主要包括加鎖pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖 pthread_mutex_trylock()三個,不論哪種類型的鎖,都不可能被兩個不同的線程同時得到,而必須等待解鎖。對於普通鎖和適應鎖類型,解鎖者可以是同進程內任何線程;而檢錯鎖則必須由加鎖者解鎖才有效,否則返回EPERM;對於嵌套鎖,文檔和實現要求必須由加鎖者解鎖,但實驗結果表明並沒有這種限制,這個不同目前還沒有得到解釋。在同一進程中的線程,如果加鎖后沒有解鎖,則任何其他線程都無法再獲得鎖。
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經被占據時返回EBUSY而不是掛起等待。
4.死鎖
死鎖主要發生在有多個依賴鎖存在時, 會在一個線程試圖以與另一個線程相反順序鎖住互斥量時發生. 如何避免死鎖是使用互斥量應該格外注意的東西。
總體來講, 有幾個不成文的基本原則:
對共享資源操作前一定要獲得鎖。
完成操作以后一定要釋放鎖。
盡量短時間地占用鎖。
如果有多鎖, 如獲得順序是ABC連環扣, 釋放順序也應該是ABC。
線程錯誤返回時應該釋放它所獲得的鎖。
下面是一段測試代碼,創建兩個線程,分別訪問全局變量gnum,並且修改它,打印出來.
1 /* mutex.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <pthread.h> 5 #include <errno.h> 6 7 /* 全局變量 */ 8 int gnum = 0; 9 /* 互斥量 */ 10 pthread_mutex_t mutex; 11 12 /* 聲明線程運行服務程序. */ 13 static void pthread_func_1(void); 14 static void pthread_func_2(void); 15 16 int main (void) 17 { 18 /*線程的標識符*/ 19 pthread_t pt_1 = 0; 20 pthread_t pt_2 = 0; 21 int ret = 0; 22 23 /* 互斥初始化. */ 24 pthread_mutex_init(&mutex, NULL); 25 /*分別創建線程1、2*/ 26 ret = pthread_create(&pt_1, //線程標識符指針 27 NULL, //默認屬性 28 (void*)pthread_func_1, //運行函數 29 NULL); //無參數 30 if (ret != 0) 31 { 32 perror ("pthread_1_create"); 33 } 34 35 ret = pthread_create(&pt_2, //線程標識符指針 36 NULL, //默認屬性 37 (void *)pthread_func_2, //運行函數 38 NULL); //無參數 39 if (ret != 0) 40 { 41 perror ("pthread_2_create"); 42 } 43 /*等待線程1、2的結束*/ 44 pthread_join(pt_1, NULL); 45 pthread_join(pt_2, NULL); 46 47 printf ("main programme exit!/n"); 48 return 0; 49 } 50 51 /*線程1的服務程序*/ 52 static void pthread_func_1(void) 53 { 54 int i = 0; 55 56 for (i=0; i<3; i++) { 57 printf ("This is pthread_1!/n"); 58 pthread_mutex_lock(&mutex); /* 獲取互斥鎖 */ 59 /* 注意,這里以防線程的搶占,以造成一個線程在另一個線程sleep時多次訪問互斥資源,所以sleep要在得到互斥鎖后調用. */ 60 sleep (1); 61 /*臨界資源*/ 62 gnum++; 63 printf ("Thread_1 add one to num:%d/n", gnum); 64 pthread_mutex_unlock(&mutex); /* 釋放互斥鎖. */ 65 } 66 67 pthread_exit(NULL); 68 } 69 70 /*線程2的服務程序*/ 71 static void pthread_func_2(void) 72 { 73 int i = 0; 74 75 for (i=0; i<5; i++) { 76 printf ("This is pthread_2!/n"); 77 pthread_mutex_lock(&mutex); /* 獲取互斥鎖. */ 78 /* 注意,這里以防線程的搶占,以造成一個線程在另一個線程sleep時多次訪問互斥資源,所以sleep要在得到互斥鎖后調用. */ 79 sleep(1); 80 /* 臨界資源. */ 81 gnum++; 82 printf ("Thread_2 add one to num:%d/n",gnum); 83 pthread_mutex_unlock(&mutex); /* 釋放互斥鎖. */ 84 } 85 86 pthread_exit (NULL); 87 }