【转载】同步和互斥的POSIX支持(互斥锁,条件变量,自旋锁)
同步對象是內存中的變量屬于進程中的資源,可以按照與訪問數據完全相同的方式對其進行訪問。默認情況下POSIX定義的這些同步對象具有進程可見性,即同步對象只對定義它的進程可見;但是通過修改同步對象的屬性可以使得同步對象對不同的進程可見,具體的做法是: 修改同步對象的屬性為PTHREAD_PROCESS_SHARED 在進程的特殊內存區域--共享內存中創建同步對象 這樣創建的同步對象將對共享該共享內存的所有進程可見,這些進程可以使用該同步對象進行同步互斥。
其中設置共享對象的屬性為PTHREAD_PROCESS_SHARED是為了告訴系統該共享對象是跨越進程的,不僅僅對創建它的進程可見;但是僅有這一個條件顯然無法滿足不同進程使用該同步對象的需求,因為每個進程的地址空間是獨立的,位于一個進程的普通內存區域中的對象是無法被其它進程所訪問的,能滿足這一要求的內存區域是共享內存,因而同步對象要在進程的共享內存區域內創建。
同步對象還可以放在文件中。同步對象可以比創建它的進程具有更長的生命周期。 POSIX定義的同步對象包括:
總體上來說,可以將它們分為兩類:?
- 信號量可以由發起P操作的線程發起V操作,也可以由其它線程發起V操作;但是條件變量一般要由其它線程發起signal(即喚醒)操作
- 由于條件變量并沒有包含任何需要檢測的條件的信息,因而對這個條件需要用其它方式來保護,所以條件變量需要和互斥鎖一起使用,而信號量本身就包含了相關的條件信息(一般是資源可用量),因而不需要和其它方式一起來使用
- 類似于三種鎖,信號量的P操作要么成功返回,要么失敗返回,不會因為出現信號而返回;但是條件變量可能因為出現信號而返回,這也是因為它沒包含相關的條件信息而導致的。(注意:無名信號量也會被信號中斷,見:http://www.cnblogs.com/charlesblc/p/6142868.html)
一、互斥鎖
如果有多個線程在等待一個互斥鎖,則在持有互斥鎖的線程釋放鎖后鎖將被等待鎖的線程中具有最高優先級的那個獲得,如果最高優先級線程有多個,則這些線程中誰將獲得鎖是不確定的。
1)初始化互斥鎖
如果互斥鎖變量是靜態的則可以直接用PTHREAD_MUTEX_INITIALIZER來初始化它,比如:
static pthread_mutex_t my_lock = PTHREAD_MUTEX_INITIALIZER
如果互斥鎖變量是動態分配的,則必須在使用它之前用pthread_mutex_init來初始化它.
pthread_mutex_init用于初始化互斥鎖,如果mattr為NULL則用缺省值初始化由mp所指向的互斥鎖,否則使用指定的mattr初始化互斥鎖。
使用PTHREAD_MUTEX_INITIALIZER與動態分配具有null 屬性的 pthread_mutex_init等效,不同之處在于PTHREAD_MUTEX_INITIALIZER 宏不進行錯誤檢查。
如果使用pthread_mutex_init初始化互斥鎖,并且指定的mattr具有PTHREAD_MUTEX_ROBUST_NP屬性,則互斥鎖所使用的內存必須在調用pthread_mutex_init之前被清0.
在有線程正在使用互斥鎖時,不能重新初始化互斥鎖或銷毀它。
1)使互斥保持一致
如果一個互斥鎖的持有者沒有釋放該鎖退出了,則在默認情況下當其它線程再去獲取這個鎖的時候,就會阻塞從而造成死鎖。可以更改互斥鎖的屬性來改變這種默認的方式:
pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_INHERIT);
pthread_mutexattr_setrobust_np(&mattr,PTHREAD_MUTEX_ROBUST_NP);
通過設置鎖的上面兩個屬性,互斥鎖就不再具有默認的行為,當一個鎖的owner死掉后,其它線程再去獲取這個鎖的時候,不會被阻塞,而是會獲得這個錯,但是同時會得到一個EOWNERDEAD的錯誤。
然后獲得鎖的線程可以嘗試處理這個錯誤:
這樣該鎖的行為就恢復正常了。
如果pthread_mutex_consistent_np在恢復鎖的一致性時候沒有成功,步驟c就不能再執行了,鎖也不能被使用了,而且接下來的線程在獲取鎖都無法獲得該鎖,而是只能得到返回值ENOTRECOVERABLE。
如果獲取某個鎖的時候得到了ENOTRECOVERABLE的錯誤,就意味這這個鎖不能被使用了,此時只能調用pthread_mutex_destroy銷毀互斥鎖然后再調用pthread_mutex_int重新初始化該互斥鎖,之后才能再使用該互斥鎖。
?
3)鎖定(獲取)互斥鎖
pthread_mutex_lock可以鎖定指定的互斥鎖。
當它返回時,該互斥鎖已被鎖定,調用它的線程就獲得了這個互斥鎖。如果該互斥鎖已被另一個線程鎖定和擁有,則調用線程將阻塞,直到該互斥鎖變為可用為止。
4)解除互斥鎖鎖定(釋放互斥鎖)
pthread_mutex_unlock可以解除指定互斥鎖的鎖定即釋放互斥鎖。
5)嘗試鎖定(獲取)互斥鎖
pthread_mutex_trylock可以嘗試鎖定指定的互斥鎖。
pthread_mutex_trylock是 pthread_mutex_lock的非阻塞版本。如果 mutex 所引用的互斥對象當前被任何線程鎖定,則將立即返回該調用。否則,該互斥鎖將被鎖定,調用線程成為其持有者。
6)銷毀互斥鎖
pthread_mutex_destroy可以銷毀與指定的互斥鎖相關聯的任何狀態。
7)初始化互斥鎖屬性對象
互斥鎖具有一些屬性,通過修改這些屬性可以控制鎖的一些行為。缺省的互斥鎖屬性及其值如下:
- pshared: ? ? ? ? ?PTHREAD_PROCESS_PRIVATE
- type: ? ? ? ? ? ? ? ? ?PTHREAD_MUTEX_DEFAULT
- protocol: ? ? ? ? ? PTHREAD_PRIO_NONE
- prioceiling: ? ? ? –?
- robustness: ? ?PTHREAD_MUTEX_STALLED_NP
可以用pthread_mutexattr_init將與互斥鎖對象相關聯的屬性初始化為其缺省值。pthread_mutexattr_init的參數類型實際上是opaque的,其中包含一個由系統分配的屬性對象。該函數執行過程中會為屬性對象分配所需的內存,因而如果未通過pthread_mutexattr_destroy銷毀互斥鎖屬性對象時就會導致內存泄漏。
對于互斥鎖屬性對象,必須首先通過調用pthread_mutexattr_destroy將其銷毀,才能重新初始化該對象。
?
9)設置/獲取互斥鎖的作用域屬性
函數pthread_mutexattr_setpshared用來設置互斥鎖的作用域。
互斥鎖變量可以是進程專用的變量,也可以是跨越進程邊界的變量。
范圍屬性的取值及其含義:
- PTHREAD_PROCESS_SHARED:具有該屬性的互斥鎖可以在多個進程中的線程之間共享。
- PTHREAD_PROCESS_PRIVATE:只有創建本互斥鎖的線程所在的進程內的線程才能夠使用該互斥鎖變量。該值是缺省值。
函數pthread_mutexattr_getpshared可用來返回由 pthread_mutexattr_setpshared設置的互斥鎖變量的范圍。
10)設置/獲取互斥鎖的類型屬性
pthread_mutexattr_settype用來設置指定互斥鎖的類型屬性。類型屬性的缺省值為 PTHREAD_MUTEX_DEFAULT。
互斥鎖的類型及其行為:
?
對比下信號,信號可以做到通知其它線程某件事發生了,接收信號的線程只需要注冊一個信號處理函數,然后信號發生后該處理函數就會被系統調用,一旦該函數被調用了就意味著注冊時關聯的信號所代表的事情發生了。但要注意:
?
?
4)在指定的時間之前阻塞
pthread_cond_timedwait的用法與 pthread_cond_wait的用法基本相同,區別在于在由abstime指定的時間之后不再被阻塞。
pthread_cond_reltimedwait_np與pthread_cond_timedwait基本相同,它們唯一的區別在于pthread_cond_reltimedwait_np使用相對時間間隔而不是將來的絕對時間作為其最后一個參數的值。
類似于pthread_cond_wait,pthread_cond_reltimedwait_np和pthread_cond_timedwait也是取消點。
?
1.基本概念
自旋鎖是SMP架構中的一種low-level的同步機制。
當線程A想要獲取一把自選鎖而該鎖又被其它線程鎖持有時,線程A會在一個循環中自選以檢測鎖是不是已經可用了。對于自選鎖需要注意:
- 由于自旋時不釋放CPU,因而持有自旋鎖的線程應該盡快釋放自旋鎖,否則等待該自旋鎖的線程會一直在那里自旋,這就會浪費CPU時間。
- 持有自旋鎖的線程在sleep之前應該釋放自旋鎖以便其它線程可以獲得自旋鎖。(在內核編程中,如果持有自旋鎖的代碼sleep了就可能導致整個系統掛起,最近剛解決了一個內核中的問題就是由于持有自旋鎖時sleep了,然后導致所有的核全部掛起(是一個8核的CPU))
使用任何鎖需要消耗系統資源(內存資源和CPU時間),這種資源消耗可以分為兩類:
對于自旋鎖來說,它只需要消耗很少的資源來建立鎖;隨后當線程被阻塞時,它就會一直重復檢查看鎖是否可用了,也就是說當自旋鎖處于等待狀態時它會一直消耗CPU時間。
?
對于自旋鎖來說,它只需要消耗很少的資源來建立鎖;隨后當線程被阻塞時,它就會一直重復檢查看鎖是否可用了,也就是說當自旋鎖處于等待狀態時它會一直消耗CPU時間。
對于互斥鎖來說,與自旋鎖相比它需要消耗大量的系統資源來建立鎖;隨后當線程被阻塞時,線程的調度狀態被修改,并且線程被加入等待線程隊列;最后當鎖可用時,在獲取鎖之前,線程會被從等待隊列取出并更改其調度狀態;但是在線程被阻塞期間,它不消耗CPU資源。
因此自旋鎖和互斥鎖適用于不同的場景。自旋鎖適用于那些僅需要阻塞很短時間的場景,而互斥鎖適用于那些可能會阻塞很長時間的場景。
?
2.API
POSIX定義的自旋鎖的數據類型是: pthread_spinlock_t
相關API?
?
1)初始化自旋鎖
pthread_spin_init用來申請使用自旋鎖所需要的資源并且將它初始化為非鎖定狀態。pshared的取值及其含義:
- PTHREAD_PROCESS_SHARED:該自旋鎖可以在多個進程中的線程之間共享。
- PTHREAD_PROCESS_PRIVATE:僅初始化本自旋鎖的線程所在的進程內的線程才能夠使用該自旋鎖。
2)獲得一個自旋鎖
pthread_spin_lock用來獲取(鎖定)指定的自旋鎖. 如果該自旋鎖當前沒有被其它線程所持有,則調用該函數的線程獲得該自旋鎖. 否則該函數在獲得自旋鎖之前不會返回。如果調用該函數的線程在調用該函數時已經持有了該自旋鎖,則結果是不確定的。
3)嘗試獲取一個自旋鎖
pthread_spin_trylock會嘗試獲取指定的自旋鎖,如果無法獲取則理解返回失敗
4)釋放(解鎖)一個自旋鎖
pthread_spin_unlock用于釋放指定的自旋鎖
5)銷毀一個自旋鎖
pthread_spin_destroy用來銷毀指定的自旋鎖并釋放所有相關聯的資源(所謂的所有指的是由pthread_spin_init自動申請的資源)在調用該函數之后如果沒有調用pthread_spin_init重新初始化自旋鎖,則任何嘗試使用該鎖的調用的結果都是未定義的。如果調用該函數時自旋鎖正在被使用或者自旋鎖未被初始化則結果是未定義的。
?
?
另外,網上找了蠻久,也沒有找到合適的利用共享內存的mutex跨進程共享應用,只找到父子進程間的mutex跨進程應用。
可能的確跨進程而言,mutex不如信號量、共享內存等來得更方便吧。
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h>typedef struct _FOO {int nCount;int nData; }FOO,*PFOO;int main(int argc,char *argv[]) {FOO *ptr;pid_t pid;pthread_mutexattr_t mutexattr;pthread_mutex_t mutex;pthread_mutexattr_init(&mutexattr);pthread_mutexattr_setpshared(&mutexattr,PTHREAD_PROCESS_SHARED); //設置為進程共享 pthread_mutex_init(&mutex,&mutexattr);ptr = (PFOO)mmap(NULL,sizeof(FOO),PROT_READ | PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0); //匿名內存映射,讓父子進程都操作ptr指向的內存區,如果不使用共享內存,則父子進程的ptr指向的是各自的內存空間ptr->nCount = 1;ptr->nData = 2;printf("%d,%d\n",ptr->nCount,ptr->nData);if( (pid = fork()) < 0){printf("fork error\n");return -1;} else if( 0 == pid) //子進程 {for(int i = 0;i<3;i++){pthread_mutex_lock(&mutex);ptr->nCount++;printf("child ++ === %d\n",ptr->nCount);pthread_mutex_unlock(&mutex);usleep(1000);}}else //父進程 {for(int i = 0;i<3;i++){pthread_mutex_lock(&mutex);ptr->nCount += 2;printf("parent +2 === %d\n",ptr->nCount);pthread_mutex_unlock(&mutex);usleep(1000);} }waitpid(pid,NULL,0);munmap(NULL,sizeof(FOO));return 0; }?
總結
以上是生活随笔為你收集整理的【转载】同步和互斥的POSIX支持(互斥锁,条件变量,自旋锁)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python分析nginx日志
- 下一篇: 原文:我的数据库学习“曲线”