互斥量
#include <pthread.h> pthread_mutex_t mutex=PTHREAD_MUTEX_INTIIALIZER; int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_mutex_destroy(pthread_mutex_t *mutex);
注意:
- 不能拷貝互斥量,可以拷貝指向互斥量的指針,這樣可以使多個線程或函數共享互斥量來同步
- 應該在函數體外生命互斥量,若沒有外部文件使用,就聲明為靜態類型;若有其他文件使用,就聲明為外部類型,使用PTHREAD_MUTEX_INITIALIZER宏聲明默認屬性的靜態互斥量。
- 使用malloc分配一個包含互斥量的數據結構時,用pthread_init_mutex來初始化,用...destory銷毀,只能初始化一次。
- 將互斥量與要保護的數據聯系在一起(定義在一個結構體中)
- 當沒有線程在乎斥量上阻塞時,立即釋放。在剛解鎖互斥量的線程內若以后不再使用釋放
- 互斥量較少時程序運行的較快,所以互斥量保護的臨界區應該較大
- 如互斥量保護的代碼包含無關的片段,應該把互斥量分解為幾個較小的互斥量來提高性能
- 多線程同時訪問隊列時使用兩個互斥量:一個保護隊頭,一個隊列元素內的數據
- 多線程同時訪問樹形結構時,應為每個節點建立個互斥量
加鎖互斥量
#include <pthread.h> int pthread_mutex_tlock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthreadd_mutex_t *mutex);
注意:
- pthread_mutex_trylock會嘗試對互斥量加鎖,如果該互斥量已經被鎖住,函數調用失敗,返回EBUSY,否則加鎖成功返回0,線程不會被阻塞。
- 當調用線程使用pthread_mutex_lock加鎖互斥量時,若互斥量被鎖住,調用線程阻塞
- 如果調用線程將互斥量鎖住,不能再加鎖該互斥量,這樣做會反悔錯誤EDEADLK,或陷入死鎖
- 原子性:當多個線程運行在多個處理器上,其他線程不會發現被破壞的不變量
- 固定加鎖層次:當數據上需要使用兩個獨立的互斥量時,應該先加鎖互斥量A再加鎖互斥量B。
- 試加鎖和回退:鎖住某個集合中的第一個互斥量時用pthread_mutex_trylock來加鎖集合中的其他互斥量,如果失敗將集合中的以加鎖互斥量釋放
- 正常的工作模式是加鎖與解鎖順序一致,若使用試加鎖和回退算法,應該以與加鎖相反的方向解鎖互斥量,有利於減少回退操作性的可能
- 鏈鎖:兩個鎖的作用范圍相互交疊。當鎖住一個互斥量后進入一個代碼區,該區域需要另個互斥量,當鎖住另個互斥量后第一個互斥量不再需要,就可以釋放(用作遍歷樹形結構或鏈表,為每個節點創建一個互斥量,首先鎖住隊頭或根節點,然后找到期望的節點鎖住他,然后釋放根節點或隊頭互斥量);搜索某個節點時使用鏈鎖(多個線程活躍在層次的不同部分時)。平衡或修剪樹時使用層次鎖。
- POSIX線程鎖機制的Linux實現都不是取消點,因此,延遲取消類型的線程不會因收到取消信號而離開加鎖等待。
- 線程在加鎖后解鎖前被取消,鎖將永遠保持鎖定狀態。因此如果在關鍵區段內有取消點存在,或者設置了異步取消類型,則必須在退出回調函數中解鎖。
- 鎖機制不是異步信號安全的,也就是說,不應該在信號處理過程中使用互斥鎖,否則容易造成死鎖。
互斥鎖屬性
//獲得/修改共享互斥量屬性 pthread_mutexattr_t attr; int pthread_mutexattr_init(pthread_mutexattr_t *attr); int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr,int *pshared); int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);
pshared的取值
- THREAD_PROCESS_SHARED:那么由這個屬性對象創建的互斥鎖將被保存在共享內存中,可以被多個進程中的線程共享。
- PTHREAD_PROCESS_PRIVATE:那么只有和創建這個互斥鎖的線程在同一個進程中的線程才能訪問這個互斥鎖。
互斥鎖類型
//獲得/修改類型互斥量屬性 int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int kind); int pthread_mutexattr_gettype(pthread_mutexattr_t *attr,int *kind);
- PTHREAD_MUTEX_NORMAL,不進行deadlock detection(死鎖檢測)。企圖進行relock這個mutex會導致deadlock.如果一個線程對未加鎖的或已經unlock的mutex對象進行unlock操作,結果是不未知的。
- PTHREAD_MUTEX_ERRORCHECK,那么將進行錯誤檢查。如果一個線程企圖對一個已經鎖住的mutex進行relock,將返回一個錯誤。如果一個線程對未加鎖的或已經unlock的mutex對象進行unlock操作,將返回一個錯誤。
- PTHREAD_MUTEX_RECURSIVE,mutex會有一個鎖住次數(lock count)的概念。當一個線程成功地第一次鎖住一個mutex的時候,鎖住次數(lock count)被設置為1,每一次一個線程unlock這個mutex的時候,鎖住次數(lock count)就減1。當鎖住次數(lock count)減少為0的時候,其他線程就能獲得該mutex鎖了。如果一個線程對未加鎖的或已經unlock的mutex對象進行unlock操作,將返回一個錯誤,如果一個線程對這種類型的互斥鎖重復上鎖,不會引起死鎖,一個線程對這類互斥鎖的多次重復上鎖必須由這個線程來重復相同數量的解鎖,這樣才能解開這個互斥鎖,別的線程才能得到這個互斥鎖。如果試圖解鎖一個由別的線程鎖定的互斥鎖將會返回一個錯誤代碼。如果一個線程試圖解鎖已經被解鎖的互斥鎖也將會返回一個錯誤代碼。這種類型的互斥鎖只能是進程私有的(作用域屬性為PTHREAD_PROCESS_PRIVATE)。
- PTHREAD_MUTEX_DEFAULT,企圖遞歸的獲取這個mutex的鎖的結果是不確定的。unlock一個不是被調用線程鎖住的mutex的結果也是不確定的。企圖unlock一個未被鎖住的mutex導致不確定的結果。
互斥鎖協議屬性
int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol); int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr, int *protocol);
- PTHREAD_PRIO_NONE:線程的優先級和調度不會受到互斥鎖擁有權的影響。
- PTHREAD_PRIO_INHERIT:當高優先級的等待低優先級的線程鎖定互斥量時,低優先級的線程以高優先級線程的優先級運行。這種方式將以繼承的形式傳遞。當線程解鎖互斥量時,線程的優先級自動被將到它原來的優先級。(“優先級繼承”意味着,當一個線程在由另一個低優先級線程擁有的互斥量上等待時,后者的優先級將被增加到等待線程的優先級.)
- PTHREAD_PRIO_PROTECT:擁有該類型的互斥量的線程將以自己的優先級和它擁有的互斥量的線程將以自己的優先級和它擁有的互斥量的優先級較高者運行,其他等待該線程擁有的鎖得線程對該線程的調度優先級沒有影響。
PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 只有在采用實時調度策略SCHED_FIFO 或SCHED_RR的優先級進程內可用。
一個線程可以同時擁有多個混合使用PTHREAD_PRIO_INHERIT 和PTHREAD_PRIO_PROTECT協議屬性初始化的互斥鎖。在這種情況下,該線程將以通過其中任一協議獲取的最高優先級執行。pthread_mutexattr_getprotocol可用來獲取互斥鎖屬性對象的協議屬性。
互斥鎖對象優先級屬性
int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr, int prioceiling, int *oldceiling); int pthread_mutexattr_getprioceiling(const pthread_mutexatt_t *attr, int *prioceiling);
prioceiling指定已初始化互斥鎖的優先級上限。優先級上限定義執行互斥鎖保護的臨界段時的最低優先級。prioceiling 位於SCHED_FIFO 所定義的優先級的最大范圍內。要避免優先級倒置,請將prioceiling 設置為高於或等於可能會鎖定特定互斥鎖的所有線程的最高優先級。oldceiling 用於返回以前的優先級上限值。
pthread_mutex_setprioceiling可更改互斥鎖mutex的優先級上限prioceiling。
pthread_mutex_setprioceiling可鎖定互斥鎖(如果未鎖定的話),或者一直處於阻塞狀態,直到它成功鎖定該互斥鎖,更改該互斥鎖的優先級上限並將該互斥鎖釋放為止。鎖定互斥鎖的過程無需遵循優先級保護協議。
如果 pthread_mutex_setprioceiling成功,則將在 old_ceiling 中返回以前的優先級上限值。如果pthread_mutex_setprioceiling失敗,則互斥鎖的優先級上限保持不變。pthread_mutex_getprioceiling會返回mutex 的優先級上限prioceiling。
注意:“優先級上限”協議意味着當一個線程擁有互斥量時,它將以指定的優先級運行。
避免死鎖
該函數允許線程阻塞特定時間,如果加鎖失敗就會返回ETIMEDOUT
#include <pthread.h> #include <time.h> int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timesec *restrict tsptr);
互斥鎖的優先級
#include <pthread.h> int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol); int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr,int *protocol);
attr 指示以前調用 pthread_mutexattr_init() 時創建的互斥鎖屬性對象。protocol 可定義應用於互斥鎖屬性對象的協議。
protocol 可以是以下值之一:PTHREAD_PRIO_NONE、PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT。
- PTHREAD_PRIO_NONE,線程的優先級和調度不會受到互斥鎖擁有權的影響。
- PTHREAD_PRIO_INHERIT,當高優先級的等待低優先級的線程鎖定互斥量時,低優先級的線程以高優先級線程的優先級運行。這種方式將以繼承的形式傳遞。當線程解鎖互斥量時,線程的優先級自動被將到它原來的優先級。(“優先級繼承”意味着,當一個線程在由另一個低優先級線程擁有的互斥量上等待時,后者的優先級將被增加到等待線程的優先級.),此協議值(如 thrd1)會影響線程的優先級和調度。如果更高優先級的線程因 thrd1 所擁有的一個或多個互斥鎖而被阻塞,而這些互斥鎖是用 PTHREAD_PRIO_INHERIT 初始化的,則 thrd1 將以高於它的優先級或者所有正在等待這些互斥鎖(這些互斥鎖是 thrd1指所擁有的互斥鎖)的線程的最高優先級運行。如果 thrd1 因另一個線程 (thrd3) 擁有的互斥鎖而被阻塞,則相同的優先級繼承效應會以遞歸方式傳播給 thrd3。使用 PTHREAD_PRIO_INHERIT 可以避免優先級倒置。低優先級的線程持有較高優先級線程所需的鎖時,便會發生優先級倒置。只有在較低優先級的線程釋放該鎖之后,較高優先級的線程才能繼續使用該鎖。設置 PTHREAD_PRIO_INHERIT,以便按與預期的優先級相反的優先級處理每個線程。如果為使用協議屬性值 PTHREAD_PRIO_INHERIT 初始化的互斥鎖定義了 _POSIX_THREAD_PRIO_INHERIT,則互斥鎖的屬主失敗時會執行以下操作。屬主失敗時的行為取決於 pthread_mutexattr_setrobust_np() 的 robustness 參數的值,1.解除鎖定互斥鎖。2.互斥鎖的下一個屬主將獲取該互斥鎖,並返回錯誤 EOWNERDEAD。3.互斥鎖的下一個屬主會嘗試使該互斥鎖所保護的狀態一致。如果上一個屬主失敗,則狀態可能會不一致。如果屬主成功使狀態保持一致,則可針對該互斥鎖調用 pthread_mutex_init() 並解除鎖定該互斥鎖。注:1.如果針對以前初始化的但尚未銷毀的互斥鎖調用 pthread_mutex_init(),則該互斥鎖不會重新初始化。2.如果屬主無法使狀態保持一致,請勿調用 pthread_mutex_init(),而是解除鎖定該互斥鎖。在這種情況下,所有等待的線程都將被喚醒。以后對 pthread_mutex_lock() 的所有調用將無法獲取互斥鎖,並將返回錯誤代碼 ENOTRECOVERABLE。現在,通過調用 pthread_mutex_destroy() 來取消初始化該互斥鎖,即可使其狀態保持一致。調用 pthread_mutex_init() 可重新初始化互斥鎖。3.如果已獲取該鎖的線程失敗並返回 EOWNERDEAD,則下一個屬主將獲取該鎖及錯誤代碼 EOWNERDEAD。
- PTHREAD_PRIO_PROTECT,當線程擁有一個或多個使用 PTHREAD_PRIO_PROTECT 初始化的互斥鎖時,此協議值會影響其他線程(如 thrd2)的優先級和調度。thrd2 以其較高的優先級或者以 thrd2 擁有的所有互斥鎖的最高優先級上限運行。基於被 thrd2 擁有的任一互斥鎖阻塞的較高優先級線程對於 thrd2 的調度沒有任何影響。有該類型的互斥量的線程將以自己的優先級和它擁有的互斥量的線程將以自己的優先級和它擁有的互斥量的優先級較高者運行,其他等待該線程擁有的鎖得線程對該線程的調度優先級沒有影響,如果某個線程調用 sched_setparam() 來更改初始優先級,則調度程序不會采用新優先級將該線程移到調度隊列末尾。線程擁有使用 PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT 初始化的互斥鎖。線程解除鎖定使用 PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT 初始化的互斥鎖
一個線程可以同時擁有多個混合使用 PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 初始化的互斥鎖。在這種情況下,該線程將以通過其中任一協議獲取的最高優先級執行PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 只有在采用實時調度策略SCHED_FIFO 或SCHED_RR的優先級進程內可用。
優先級繼承互斥量
當一個線程鎖住互斥量時,線程的優先級就被互斥量控制,當另外的線程在那個互斥量上阻塞時,他會查看擁有互斥量的線程優先級,如果擁有互斥量的線程比試圖在互斥量上阻塞的線程優先級低,則擁有互斥量的線程的優先級將被提升到阻塞線程的優先級。
除非等待的線程也被搶占,提高優先級確保擁有互斥量的線程不能被搶占;擁有互斥量的線程代表的是高優先級工作的線程,當線程互斥量解鎖時,線程的優先級被自動降到他原來的優先級,高優先級等待線程將被喚醒,如果又有一個高優先級線程在互斥量上阻塞,擁有互斥量的線程將再次增加優先級。
條件變量
#include <pthread.h> pthread_cond_t cond=PTHREAD_COND_INITIALIZER; int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *condattr); int pthread_cond_destroy(pthread_cond_t *cond);
- 條件變量總是返回鎖住的互斥量,是用來通知共享數據的狀態信息
- 條件變量與互斥量相關,也於互斥量保護的共享數據相關的信號機制
- 在一個條件變量上等待會導致一下原子操作:釋放相關的互斥量,等待其他線程發送給該條件變量信號(喚醒一個等待者)或廣播該條件變量(喚醒所有等待者),等待條件變量時互斥量必須始終鎖住,當線程從條件變量等待中醒來時,繼續鎖住互斥量(即:阻塞線程前,條件變量等待操作將解鎖互斥量,重新返回線程前,會再次鎖住互斥量)
- 條件變量的作用是發信號而不是互斥
- 互斥量不僅要與條件變量一起使用,還要單獨使用(不能將互斥量作為條件變量的一部分創建);一個互斥量與多個條件變量相關(如:隊列可以為空,也可以為滿,設置兩個條件變量讓線程等待不同的條件,但只有一個互斥量來協調對隊列頭的訪問)
- 一個條件變量應該與一個謂詞相關
- 信號比廣播有效(條件變量和謂詞都是程序中的共享數據,他們被多個線程使用,可能同時使用,他們被相同的互斥量鎖住,在沒有鎖住互斥量之前就發信號或廣播條件變量似乎有可能的,更安全的方式是先鎖住互斥量)
- 不能拷貝條件變量但可以傳遞條件變量的指針使不同函數和線程使用它來同步
- 若有其他文件需要使用該條件變量則聲明為extern類型否則聲明為static
- 可以將條件變量,互斥量和謂詞聲明在一個結構體中
- 在廣播了該條件變量並喚醒了所有等待線程的線程內,確定不再有線程使用可釋放該條件變量
- 線程從列表中刪除了一個包含該條件變量的節點后,廣播所有等待該條件變量的線程,此時釋放條件變量是安全的
- 並發的等待同一個條件變量的線程必須制定同一個相關的互斥量,任何條件變量在特定時刻只能與一個互斥量相關聯,互斥量可以與多個條件變量相關聯
等待條件變量、喚醒等待線程
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,struct timespec *expiration); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond);
- 在鎖住相關的互斥量之后和等待條件變量之前要測試謂詞,如果線程發信號或廣播一個條件變量時而沒有線程等待該條件變量,則什么也沒有發生,若在這之后有線程調用pthread_cond_wait,則將一直等待下去無視剛才廣播的事實,這意味該線程永遠不能被喚醒,因為在線程等待條件變量之前互斥量一直被鎖住
- 因該在循環中等待條件變,來避免程序錯誤,處理機競爭和假喚醒
- 被攔截的喚醒:線程執行時異步的,從條件變量等待中喚醒包括加鎖相應的互斥量。也就是說可能有多個線程被喚醒(被pthread_cond_broadcast喚醒),被喚醒的線程都“爭着”去處理共享數據,但只有一個線程能處理到這個共享數據,等它處理完畢后,其他線程也會進入該臨界區去處理共享數據,這個時候就會出現共享數據被多次處理的情況,我們的解決方法是在被喚醒后,在去檢查一下謂詞變量,看該共享數據是否已經被處理了。
- 松散的謂詞:有時候謂詞表示了“可能有工作”而不是“有工作”的意思。所以,當線程被喚醒,我們必須確認到底有沒有工作。
- 假喚醒:在有些系統中,我們沒有用pthread_cond_signal()或pthread_cond_broadcast()來喚醒用pthread_cond_wait()休眠的線程,但是在休眠的線程還是會被喚醒,在Linux中,被休眠的進程或線程,都有被信號(linux中的信號)打斷的可能,也就是說,如果線程或進程在休眠中,會有被喚醒的可能,所以我們在有可能引起休眠的API調用返回后都會檢查一下返回值是否為EINTR,如果是,那么線程或進程都將繼續休眠。
- tiemdwait函數等待某個時間結束后會返回ETIMEOUT狀態,時間是絕對時間。處理該錯誤之前要先檢測謂詞,若等待條件為真,那么等待長時間就不重要了。
- 不能用發信號代替廣播,使用廣播時因為等待線程會處理被攔截的喚醒,發信號與廣播真正的區別是效率:廣播喚醒額外的線程,這些線程檢測到自己的謂詞繼續等待
- 當有個線程需要被喚醒來處理改變后的狀態用發信號,如果多個謂詞條件使用同一個條件變量,則不能是使用發信號,也不能用重發信號,如果向隊列中增加一個元素,而且只有等待鈣元素的線程在條件變量上阻塞,則可使用發信號,讓其他線程繼續等待,如果增加多個元素,可能要使用廣播。
條件變量的屬性
#include < pthread.h > int pthread_condattr_init(pthread_condattr_t * attr ); int pthread_condattr_destroy(pthread_condattr_t * attr ); #ifdef _POSIX_THREAD_PROCESS_SHARED int pthread_condattr_getpshared(pthread_condattr_t *attr,int *pshared); int pthread_condattr_setpshared(pthread_condattr_t *attr,int *pshared); #endif // _POSIX_THREAD_PROCESS_SHARED
- pthread_condattr_destroy()函數應銷毀條件變量屬性對象; 實際上,對象變得未初始化。實現可能導致pthread_condattr_destroy()將attr引用的對象設置為無效值。可以使用pthread_condattr_init()重新初始化已銷毀的attr屬性對象; 在銷毀之后引用對象的結果是未定義的。
- pthread_condattr_init()函數將初始化條件變量屬性對象ATTR與所有實現中定義的屬性的缺省值。
- 如果調用pthread_condattr_init()指定已初始化的attr屬性對象,則結果未定義。
- 在使用條件變量屬性對象初始化一個或多個條件變量之后,影響屬性對象的任何函數(包括破壞)都不會影響任何先前初始化的條件變量。
- cattr 的數據類型為 opaque,其中包含一個由系統分配的屬性對象。cattr 范圍可能的值為 PTHREAD_PROCESS_PRIVATE 和 PTHREAD_PROCESS_SHARED。PTHREAD_PROCESS_PRIVATE 是缺省值。條件變量屬性必須首先由 pthread_condattr_destroy 重新初始化后才能重用。pthread_condattr_init() 調用會返回指向類型為 opaque 的對象的指針。如果未銷毀該對象,則會導致內存泄漏。
- 這兩個函數執行成功都返回0,返回其他值都是錯誤
- 為使用一個PTHREAD_PROCESS_SHARED條件變量,必須使用一個PTHREAD_PROCESS_SHARED互斥量,因為同步使用一個條件變量的兩個線程必須使用一樣的互斥量,等待一個條件變量會自動解鎖然后加鎖相關的互斥量,因此如果互斥量沒有與PTHREAD_PROCESS_SHARED一起被創建,同步不會工作。
條件變量的范圍
pthread_condattr_t cattr; int pthread_condattr_setpshared(pthread_condattr_t *cattr, int pshared);//設置條件變量的范圍 /* all processes */ ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); /* within a process */ ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_PRIVATE); int pthread_condattr_getpshared(const pthread_condattr_t *cattr,int *pshared);//獲取條件變量的范圍 //以上兩個函數成功都返回0,返回其它任何值都失敗
pshared屬性在共享內存中設置為 PTHREAD_PROCESS_SHARED,則其所創建的條件變量可以在多個進程中的線程之間共享。此行為與最初的 Solaris 線程實現中 mutex_init() 中的 USYNC_PROCESS 標志等效。如果互斥鎖的 pshared 屬性設置為 PTHREAD_PROCESS_PRIVATE,則僅有那些由同一個進程創建的線程才能夠處理該互斥鎖。PTHREAD_PROCESS_PRIVATE 是缺省值。PTHREAD_PROCESS_PRIVATE 所產生的行為與在最初的 Solaris 線程的 cond_init() 調用中使用 USYNC_THREAD 標志相同。PTHREAD_PROCESS_PRIVATE 的行為與局部條件變量相同。PTHREAD_PROCESS_SHARED 的行為與全局條件變量等效。
線程間的內存可見性
每個線程都有獨立的線程上下文,包括線程id,棧,棧指針,程序計數器,條件碼和通用目的寄存器。每個線程和其他線程共享進程虛擬地址空間,由代碼段,讀/寫數據,堆和所有的共享代碼庫和數據組成。共享打開的文件集合。
- 當線程調用pthread時,他所能看見的內存值也是它建立的線程能夠看到的,任何在pthread_create之后向內存寫入的數據可能不會被建立的線程看到,即使寫操作發生在啟動線程之前
- 當線程解鎖互斥量時看到的內存數據,同樣能被后來直接鎖住(或通過等待條件變量鎖住)相同互斥量的線程看到。同樣,在解鎖互斥量之后寫入的數據不必被其他線程看見,即使寫操作發生在其他線程鎖住互斥量之前。
- 線程終止(或通過取消操作,或從啟動函數中返回,或調用pthread_exit())時看到的內存數據,同樣能夠被鏈接該線程的其他線程(通過pthread_join())看到,終止后寫入的數據不會被其他線程看到,即使寫操作發生在連接之前
- 線程發信號或廣播條件變量時看到的內存數據,同樣可以被喚醒的其他線程看到,而在發信號或廣播之后寫入的數據不會被喚醒的線程看到,即使寫操作發生在線程被喚醒之前。
- 線程寄存器變量不能被其他線程修改;線程分配的堆棧和堆空間是私有的,除非線程將指向該內存的指針傳遞給其他線程;任何放在register和auto變量中的數據可以再隨后的某個時刻讀取,就想完全同步的數據一樣,每個線程自己是同步的,線程中共享的數據越少需要做的工作越多。
從pthread_mutex_unlock()到pthread_mutex_lock()之間可視化,當線程b從pthread_mutex_lock()中返回時,他將和線程a調用pthread_mutex_unlock()看到同樣變量的值。即相應的1和2
//線程1 pthread_mutex_lock(&mutex); a=1; b=2; pthread_mutex_unlock(&mutex); //線程2 pthread_mutex_lock(&mutex); local_a=a; local_b=b; pthread_mutex_unlock(&mutex);
從pthread_mutex_unlock()到pthread_mutex_lock()之間可視化。當線程b從pthread_mutex_lock()中返回時,他將和線程a調用pthread_mutex_unlock()看到同樣變量的值。即local_a相應1,local_b的值,因為他是在解鎖互斥量之后寫入的。
//線程1 pthread_mutex_lock(&mutex); a=1; pthread_mutex_unlock(&mutex); b=2; //線程2 pthread_mutex_lock(&mutex); local_a=a; local_b=b; pthread_mutex_unlock(&mutex);
- 線程的寄存器變量、線程分配的堆棧是私有的不能被其它線程看見。任何存放在register或auto中的變量可以在隨后的某刻讀取到。
- 如果線程設置了一個全局變量,然后創建一個線程讀取該變量,則新線承看不到舊變量(只能看到改過的新值);如果創建一個新線承然后再設置變量值,新線承可能看不到新的變量值
讀寫鎖
- 只要沒有寫模式下的加鎖,任意線程都可以進行讀模式下的加鎖這個其實可以很好理解,因為單純的讀操作不改變訪問的任何資源,多個線程都可以同時無影響的讀取資源。
- 只有讀寫鎖處於不加鎖狀態時,才能進行寫模式下的加鎖
讀寫鎖也稱為共享-(shared-exclusive)獨占鎖,當讀寫鎖以讀模式加鎖時,它是以共享模式鎖住(即任何訪問者都可以使用),當以寫模式加鎖時,它是以獨占模式鎖住(只有獲得鎖的人可以使用)
當一個寫鎖被釋放時,如果讀數據寫數據同時等待,讀數據有優先訪問權限。
讀寫鎖的類型是在內存中分配的,當讀寫鎖是在單個進程內的各個線程共享時(默認情況),這些變量可以在進程內;當這些變量是在某個共享內存區的進程間共享時(假設指定PTHREAD_PROCESS_SHARED),這些變量在共享內存中
與互斥鎖的區別
- 互斥鎖會把試圖進入已保護的臨界區的線程都阻塞,即同時只有一個線程持有鎖
- 讀寫鎖會根據當前進入臨界區的線程是讀還是寫的屬性來判斷是否允許線程進入。
- 多個讀者可以同時持有鎖
初始化
/* 用於初始化讀寫鎖,並可以設置讀寫鎖屬性, * 一般默認為NULL,如果要修改寫鎖屬性可以詳細參考鏈接 * http://docs.oracle.com/cd/E19455-01/806-5257/6je9h032t/index.html * PTHREAD_RWLOCK_INITIALIZER宏定義可以用來靜態的分配初始一個讀寫鎖,不需要錯誤檢查,分配默認 * 的讀寫鎖屬性,和pthread_rwlock_init()指定NULL屬性的效果是一致的。pthread_rwlock_destroy() * 用於銷毀讀寫鎖。 */ pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER; int pthread_rwlock_init(pthread_rwlock_t * restrict lock,const pthread_rwlockattr_t * restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *lock);
init返回值
- 成功返回0
- [EAGAIN]:系統缺少資源來初始化讀寫鎖
- [EBUSY]:嘗試初始化一個已經初始化但還未銷毀的鎖.
- [EINVAL]:attr不可用.
- [ENOMEM]:內存資源不足
destroy返回值
- 成功返回0
- [EBUSY] 嘗試銷毀一個還鎖着的鎖
- [EINVAL] 指定的鎖參數不可用
讀鎖
int pthread_rwlock_rdlock(pthread_rwlock_t *lock); int pthread_rwlock_timedrdlock(pthread_rwlock_t * restrict lock,const struct timespec * restrict abstime); int pthread_rwlock_tryrdlock(pthread_rwlock_t *lock); /* 讀寫鎖讀加鎖有3個函數接口,pthread_rwlock_rdlock()請求一個讀鎖, * 如果當前沒有寫線程持有鎖或者沒有寫線程阻塞在取鎖時則可以立刻獲取到鎖。否則請求鎖的線程阻塞等 * 待,pthread_rwlock_timedrdlock() 執行同樣的讀加鎖操作,只是有個超時時間,在指定abstime時間 *(超時指定的是絕對時間,不是相對時間)獲取不到直接返回,其中abstime的類型為struct timespec */ struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; /* pthread_rwlock_tryrdlock()執行同樣加鎖操作的非阻塞函數,獲不到鎖立即返回。 注:一個線程可能獲取到多個並發的讀鎖,如果是這樣的話,對每個加鎖都要釋放一次。 */
成功均返回0
失敗
- pthread_rwlock_tryrdlock():[EBUSY]其寫鎖正被持有或者寫鎖阻塞等待
- pthread_rwlock_timedrdlock():[ETIMEDOUT]加鎖超時
- pthread_rwlock_rdlock(), pthread_rwlock_timedrdlock(), and pthread_rwlock_tryrdlock():[EAGAIN]獲取鎖的數量已經超過最大值,[EDEADLK]當前線程已經持有寫鎖,[EINVAL]指定的鎖參數不可用
寫鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *lock); //阻塞的形式獲取一個寫鎖 int pthread_rwlock_timedwrlock(pthread_rwlock_t * restrict lock,const struct timespec * restrict abstime); //執行相同的取寫鎖的操作, 只是有個超時時間,
//在指定abstime絕度時間內獲取不到直接返回 int pthread_rwlock_trywrlock(pthread_rwlock_t *lock); //執行同樣加鎖操作的非阻塞函數,獲不到鎖立即返回。
成功均返回0
失敗
- The pthread_rwlock_trywrlock():[EBUSY]不能非阻塞的獲得鎖
- pthread_rwlock_timedwrlock():[ETIMEDOUT]加鎖超時
- pthread_rwlock_wrlock(), pthread_rwlock_timedwrlock(), and pthread_rwlock_trywrlock():[EDEADLK]調用的線程已經持有讀寫鎖了,[EINVAL]指定的鎖參數不可用
解鎖
int pthread_rwlock_unlock(pthread_rwlock_t *lock);// 改函數用來釋放獲取到的讀寫鎖。
成功均返回0
失敗
- [EINVAL]指定的鎖參數不可用
- [EPERM]當前線程沒有持有鎖