APUE 4 - 線程<2> : 線程同步

當控件的多個線程共享統一內存時,咱們須要肯定各個線程訪問到的數據的一致性。在cpu結構中,修改操做由多個內存讀寫週期(memory cycle),而在這些內存週期之間, 有可能會發生其餘線程的內存讀操做,這樣就會產生多線程之間的數據一致性問題。編程

 

互斥鎖 mutex多線程

 

咱們能夠經過線程互斥鎖接口(pthreads mutual-exclusion interfaces)來保證同一時間只有一個線程訪問咱們的數據。一個mutex變量使用pthread_mutex_t數據類型來表示。在咱們使用這個變量前,咱們必須使用常量PTHREAD_MUTEX_INITIALIZER或調用pthread_mutex_init對其進行初始化。若是咱們動態的分配mutex(經過調用pthread_mutex_init),咱們須要在釋放內存前調用pthread_mutex_destroy。架構

1 #include <pthread.h>
2 
3 /* Both return 0 if OK, error number on failure */
4 int pthread_mutex_init(pthread_mutex_t* restrict mutex,  const pthread_mutexattr_t* restrict attr);
5 
6 int pthread_mutex_destroy(pthread_mutex_t* mutex);

能夠經過 pthread_mutex_lock對mutex加鎖,若是mutex已經被鎖住,那麼其餘對mutex進行加鎖的線程會被阻塞,直到mutex被解鎖。能夠經過pthread_mutex_unlock來解鎖mutex。函數

 1 #include <pthread.h>
 2 
 3 /* return 0 if OK, error number on failure */
 4 int pthread_mutex_lock(pthread_mutex_t* mutex);
 5 
 6 /* 
 7   若是mutex處於unlocked狀態,此方法會對mutex加鎖並返回0,
 8   不然返回EBUSY,不會對mutex加鎖
 9 */
10 int pthread_mutex_trylock(pthread_mutex_t* mutex);
11 
12 /* return 0 if OK, error number on failure */
13 int pthread_mutex_unlock(pthread_mutex_t* mutex);

 

 

避免死鎖性能

一個線程若是試圖對同一個mutex進行連續兩次加鎖,那麼它將處於死鎖狀態。而確實有那麼幾種明顯的使用mutex會產生死鎖。舉例來講,當咱們程序中有多個mutex的時候,若是咱們讓一個線程鎖住第一個mutex,而後對第二個mutex進行加鎖,而與此同時若是另外一個線程鎖住第二個mutex並試圖對第一個mutex進行加鎖,那麼此時程序就會進入死鎖狀態。spa

咱們能夠經過消息的控制對mutex的加鎖狀態來避免死鎖。若是咱們全部的線程對全部的mutex都保持同一種加鎖順序,那麼程序中永遠不會出現加鎖現象。而後有些時候,因爲程序的架構問題咱們沒法保證加鎖順序,此時咱們就必須採起一些其餘的措施。在這種狀況下咱們應該先釋放掉以加鎖的mutex,而後從新嘗試加鎖。這種狀況下咱們可使用pthread_mutex_trylock,若是pthread_mutex_trylock成功咱們能夠繼續進行其餘操做,不然咱們應該釋放掉以加鎖的mutex,清理程序,而後在重試加鎖。線程

可使用pthread_mutex_timedlock方法綁定加鎖阻塞時間,若是規定時間內沒有成功對mutex加鎖,它返回ETIMEOUT。rest

1 #include <pthread.h>
2 #include <time.h>
3 
4 /* tspr is absolute time, Return 0 if OK, error number on failure. */
5 int pthread_mutex_timedlock(pthread_mutex_t* restrict mutex, const struct timespec* restrict tsptr);

 

 

讀寫鎖code

讀寫鎖也稱爲共享鎖,它與mutex類似,但他們提供了對並行機制更高精度的控制。對於mutex來說,他只有兩種狀態:locked和unlocked,而且,同一時間內只有一個線程能夠鎖住它。而對於讀寫鎖來講,他們有三種狀態:讀鎖狀態(locked in read mode),寫鎖狀態(locked in write mode)和解鎖狀態(unlocked)。同一時間只有一個線程可讓讀寫鎖處於寫鎖狀態,而同一時間能夠有多個線程讓讀寫鎖處於讀鎖狀態。對象

當一個讀寫鎖處於寫鎖狀態時,全部嘗試對其加鎖的線程都會處於阻塞狀態,知道讀寫鎖的寫鎖狀態被解除。當讀寫鎖處於讀鎖狀態時,全部對其進行寫鎖操做的線程都會被阻塞,可是對其進行讀鎖操做的線程不會被阻塞。一般,當有對處於讀寫鎖進行寫鎖操做的線程被阻塞時,其餘隊此讀寫鎖進行讀操做的線程也會被阻塞。

讀寫鎖在使用前必須初始化,一樣的,在釋放內存錢必須銷燬。也可使用常量PTHREAD_RWLOCK_INITIALIZER初始化rwlock。

 1 #include <pthread.h>
 2  
 3 /* All return 0 if OK, error number on failure */
 4 int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock, const pthread_rwlockattr_t* restrict attr);
 5 int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
 6  
 7 int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
 8 int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
 9 /* unlock any type of  rwlock */
10 int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
11 
12 int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);
13 int pthread_rwlock_truwrlock(pthread_rwlock_t* rwlock);

rwlock with timeouts

1 #include <pthread.h>
2 #include <time.h>
3 
4 int pthread_rwlock_timedrdlock(pthread_rwlock_t* restrict rwlock, const struct timespec* restrict tsptr);
5 
6 int pthread_rwlock_timedwrlock(pthread_rwlock_t* restrict rwlock, const struct timespec* restrict tsptr);

 

 

條件變量(Condition Variables)

條件變量是另外一種線程同步機制,他們提供了線程交互點。當與mutex一塊兒使用時, 條件變臉能夠是線下以自由競爭的方式來等待任意條件的發生。這個條件自己是被一個mutex保護的。線程在改變條件狀態以前必須對這個mutex加鎖,其餘線程在獲取到這個mutex權限前不會知道這個變化,由於mutex必須處於加鎖狀態才能讀取到條件的值。

條件變量在使用前必須進行初始化,咱們能夠經過常量PTHREAD_COND_INITAILIZER或者調用pthread_cond_init來初始化條件變量, 若條件變量是動態分配的,那麼我麼須要使用pthread_cond_destroy來釋放條件變量。

1 #include <pthread.h>
2 
3 /* Both return 0 if OK, error number on failure */
4 int pthread_cond_init(pthread_cond_t* restrict cond, const pthread_condattr_t* restrict attr);
5 
6 int pthread_cond_destroy(pthread_cond_t* cond);

wait for condition to be true

1 #include <pthread.h>
2 
3 int pthread_cond_wait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex);
4 
5 int pthread_cond_timedwait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex, constr struct timespec* restict tsptr);

mutex用於保護條件,以防止多個線程調用pthread_cond_wait的競態條件。調用線程在調用前須要對mutex加鎖,pthread_cond_wait會自動將調用線程放到此條件的等待線程列表中而後等待條件的發生並解鎖mutex。當phtread_cond_wait返回時(正常狀況是條件被知足時),mutex被從新置位加鎖狀態。在pthread_cond_wait或phread_cond_timedwait方法返回時,線程須要從新檢測條件,由於其餘線程在此時可能會更改了條件, 所以一般咱們將等待放入循環中。

有兩個函數能夠通知條件已知足,pthread_cond_signal能夠喚醒至少一個條件等待線程,而pthread_cond_broadcast會喚醒全部的條件等待線程。

1 #include <pthread.h>
2 
3 int pthread_cond_signal(pthread_cond_t* cond);
4 int pthread_cond_broadcast(pthread_cond_t* cond);

 

 

 

自旋鎖(spin locks)

 

自旋鎖與互斥鎖類似,只不過它不是以休眠的方式阻塞進程,而是經過busy-waiting(spinning)知道獲取到鎖權限。自旋鎖一般用於鎖住狀態時間較短和線程不肯遭受調度成本的狀況。自旋鎖一般用於實現其餘類型鎖的底層原型。依賴於系統結構,他們能夠經過test-and-set指令高效的實現。儘管高效,他們會致使浪費CPU資源。

1 #include <pthread.h>
2 
3 int pthread_spin_init(pthread_spinlock_t* lock, int pshared);
4 int pthread_spin_destroy(pthread_spinlock_t* lock);
5 
6 int pthread_spin_lock(pthread_spinlock_t* lock);
7 int pthread_spin_trylock(pthread_spinlock_t* lock);
8 int pthread_spin_unlock(pthread_spinlock_t* lock);

 

 

Barriers

Barriers 是一種同步機制,它能夠用於並行環境下協調多線程工做。barriers容許每一個線程都等待直到全部協調線程都達到某個點,而後從它阻塞的地方繼續執行。pthread_jion就是一種形式的barrier——它使一個線程等待直到另外一個線程退出。Barrier對象要比他更通用一些,他們容許任意數量的線程等待直到全部的線程完成執行,但線程沒必要退出。

1 #include <pthread.h>
2 
3 int pthread_barrier_init(pthread_barrier_t* restrict barrier, const pthread_barrierattr_t* restrict attr, unsigned int count);
4 int pthread_barrier_destroy(pthread_barrier_t* barrier);

count參數用於指定在全部線程容許繼續執行前必須達到barrier的線程數量。

咱們使用pthread_barrier_wait函數來指示此線程以完成他的工做並準備好等待其餘線程遇上進度。

#include <pthread.h>

/* Rturns 0 or PTHREAD_BARRIER_SERIAL_THREAD if OK, error number on failure */
int pthread_barrier_wait(pthread_barrier_t* barrier);

調用pthread_barrier_wait的線程會被置於休眠狀態若是若是barrier數量未達到init時設置的數量。若是調用線程是最後一個線程,即barrier數量達到了初始化是設置的數量,此時全部的線程都會被喚醒。

 

 

總結

線程基本同步機制有 mutex、rwlock、condlock、spin lock、barrier。mutex同一時間內只有一個線程處於locked狀態;rwlock提供了對讀寫更精細的控制;condlock經過條件變量爲多線程同步提供了線程交互點;spin lock是高效的mutex,可是浪費cpu性能,通常用戶層編程用不到此類型的鎖;barrier提供了一種良好的多線程協調機制;實際項目中咱們應該根據幾種鎖的特性來合理使用,並注意要避免死鎖問題。

相關文章
相關標籤/搜索