線程間的通信有兩種情況:
1、一個進程中的線程與另外一個進程中的線程通信,由於兩個線程只能訪問自己所屬進程的地址空間和資源,故等同於進程間的通信。
2、同一個進程中的兩個線程進行通信。本文說的就是第二種情況。
關於進程間通信(IPC)可以看我的另一篇博文
/content/10967720.html
比起進程復雜的通信機制(管道、匿名管道、消息隊列、信號量、共享內存、內存映射以及socket等),線程間通信要簡單的多。
因為同一進程的不同線程共享同一份全局內存區域,其中包括初始化數據段、未初始化數據段,以及堆內存段,所以線程之間可以方便、快速地共享信息。只需要將數據復制到共享(全局或堆)變量中即可。不過,要避免出現多個線程試圖同時修改同一份信息。
下圖為多線程的進程地址空間:
線程安全:
所在的進程中有多個線程在同時運行,而這些線程可能會同時某一段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。線程安全就是說多線程訪問同一段代碼不會產生不確定的結果。編寫線程安全的代碼依靠線程同步。
線程間的同步:
如果變量時只讀的,多個線程同時讀取該變量不會有一致性問題,但是,當一個線程可以修改的變量,其他線程也可以讀取或者修改的時候,我們就需要對這些線程進行同步,確保它們在訪問變量的存儲內容時不會訪問到無效的值。
1. 互斥鎖
互斥量本質上說是一把鎖,在訪問共享資源前對互斥量進行加鎖,在訪問完成後釋放互斥量。對互斥量進行枷鎖以後,其他視圖再次對互斥量加鎖的線程都會被阻塞直到當前線程釋放該互斥鎖。如果釋放互斥量時有一個以上的線程阻塞,那麼所有該鎖上的阻塞線程都會變成可運行狀態,第一個變成運行狀態的線程可以對互斥量加鎖,其他線程就會看到互斥量依然是鎖著,只能再次阻塞等待它重新變成可用,這樣,一次只有一個線程可以向前執行。
常用頭文件:
#include <pthread.h>
常用函數:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);//互斥初始化
int pthread_mutex_destroy(pthread_mutex_t *mutex);//銷毀互斥
int pthread_mutex_lock(pthread_mutex_t *mutex);//鎖定互斥
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解鎖互斥
int pthread_mutex_trylock(pthread_mutex_t *mutex);//銷毀互斥
eg.pthread_t mutex;
pthread_mutex_init(&mutex, NULL);
pthread_mutex_lock(&mutex);
...
pthread_mutex_unlock(&mutex);
pthread_mutex_detroy(&mutex);
互斥量的死鎖:
一個線程需要訪問兩個或者更多不同的共享資源,而每個資源又有不同的互斥量管理。當超過一個線程加鎖同一組互斥量時,就可能發生死鎖。死鎖就是指多個線程/進程因競爭資源而造成的一種僵局(相互等待),若無外力作用,這些進程都將無法向前推進。
死鎖的處理策略:
1、預防死鎖:破壞死鎖產生的四個條件:互斥條件、不剝奪條件、請求和保持條件以及循環等待條件。
2、避免死鎖:在每次進行資源分配前,應該計算此次分配資源的安全性,如果此次資源分配不會導致系統進入不安全狀態,那麼將資源分配給進程,否則等待。算法:銀行家算法。
3、檢測死鎖:檢測到死鎖後通過資源剝奪、撤銷進程、進程回退等方法解除死鎖。
2. 讀寫鎖
讀寫鎖與互斥量類似,不過讀寫鎖擁有更高的並行性。互斥量要麼是鎖住狀態,要麼是不加鎖狀態,而且一次只有一個線程可以對其加鎖。讀寫鎖有3種狀態:讀模式下加鎖狀態,寫模式下加鎖狀態,不加鎖狀態。一次只有一個線程可以占有寫模式的讀寫鎖,但是多個線程可以同時占有讀模式的讀寫鎖。
當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有視圖對這個鎖加鎖的線程都會被阻塞。當讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對它進行加鎖的線程都可以得到訪問權,但是任何希望以寫模式對此鎖進行加鎖的線程都會阻塞,直到所有的線程釋放它們的讀鎖為止。
常用頭文件:
#include <pthread.h>
常用函數:
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *rwlockattr);//初始化讀寫鎖
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//銷毀讀寫鎖
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//讀模式鎖定讀寫鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//寫模式鎖定讀寫鎖
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//解鎖讀寫鎖
eg.pthread_rwlock_t q_lock;
pthread_rwlock_init(&q_lock, NULL);
pthread_rwlock_rdlock(&q_lock);
...
pthread_rwlock_unlock(&q_lock);
pthread_rwlock_detroy(&q_lock);
3. 條件變量
條件變量是線程可用的另一種同步機制。互斥量用於上鎖,條件變量則用於等待,並且條件變量總是需要與互斥量一起使用,運行線程以無競爭的方式等待特定的條件發生。
條件變量本身是由互斥量保護的,線程在改變條件變量之前必須首先鎖住互斥量。其他線程在獲得互斥量之前不會察覺到這種變化,因為互斥量必須在鎖定之後才能計算條件。
常用頭文件:
#include <pthread.h>
常用函數:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);//初始化條件變量
int pthread_cond_destroy(pthread_cond_t *cond);//銷毀條件變量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);//無條件等待條件變量變為真
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *tsptr);//在給定時間內,等待條件變量變為真
eg.pthread_mutex_t mutex;
pthread_cond_t cond;
...
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
...
pthread_mutex_unlock(&mutex);
...
注意: pthread_cond_wait 執行的流程首先將這個mutex解鎖, 然後等待條件變量被喚醒, 如果沒有被喚醒, 該線程將一直休眠, 也就是說, 該線程將一直阻塞在這個pthread_cond_wait調用中, 而當此線程被喚醒時, 將自動將這個mutex加鎖,然後再進行條件變量判斷(原因是“驚群效應”,如果是多個線程都在等待這個條件,而同時只能有一個線程進行處理,此時就必須要再次條件判斷,以使只有一個線程進入臨界區處理。),如果滿足,則線程繼續執行。
4. 信號量線程的信號和進程的信號量類似,使用線程的信號量可以高效地完成基於線程的資源計數。信號量實際上是一個非負的整數計數器,用來實現對公共資源的控制。在公共資源增加的時候,信號量就增加;公共資源減少的時候,信號量就減少;只有當信號量的值大於0的時候,才能訪問信號量所代表的公共資源。
常用頭文件:
#include <semaphore.h>
常用函數:
sem_t sem_event;
int sem_init(sem_t *sem, int pshared, unsigned int value);//初始化一個信號量
int sem_destroy(sem_t * sem);//銷毀信號量
int sem_post(sem_t * sem);//信號量增加1
int sem_wait(sem_t * sem);//信號量減少1
int sem_getvalue(sem_t * sem, int * sval);//獲取當前信號量的值
互斥與同步的區別:
互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即
訪問是無序的。
同步:主要是流程上的概念,是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者
對資源的有序訪問。
在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源。
互斥鎖、條件變量和信號量的區別:互斥鎖:
互斥,一個線程占用了某個資源,那麼其它的線程就無法訪問,直到這個線程解鎖,其它線程才可以訪問。
條件變量:
同步,一個線程完成了某一個動作就通過條件變量發送信號告訴別的線程,別的線程再進行某些動作。條件變量必須和互斥鎖配合使用。
信號量:
同步,一個線程完成了某一個動作就通過信號量告訴別的線程,別的線程再進行某些動作。而且信號量有一個更加強大的功能,信號量可以用作為資源計數器,把信號量的值初始化為某個資源當前可用的數量,使用一個之後遞減,歸還一個之後遞增。
另外還有以下幾點需要注意:
1、
信號量可以模擬條件變量,因為條件變量和互斥量配合使用,相當於信號量模擬條件變量和互斥量的組合。在生產者消費者線程池中,生產者生產數據後就會發送一個信號 pthread_cond_signal通知消費者線程,消費者線程通過pthread_cond_wait等待到了信號就可以繼續執行。這是用條件變量和互斥鎖實現生產者消費者線程的同步,用信號量一樣可以實現!
2、
信號量可以模擬互斥量,因為互斥量只能為加鎖或解鎖(0 or 1),信號量值可以為非負整數,也就是說,一個互斥量只能用於一個資源的互斥訪問,它不能實現多個資源的多線程互斥問題。信號量可以實現多個同類資源的多線程互斥和同步。當信號量為單值信號量時,就完成一個資源的互斥訪問。前面說了,信號量主要用做多線程多任務之間的同步,而同步能夠控制線程訪問的流程,當信號量為單值時,必須有線程釋放,其他線程才能獲得,同一個時刻只有一個線程在運行(注意,這個運行不一定是訪問資源,可能是計算)。如果線程是在訪問資源,就相當於實現了對這個資源的互斥訪問。
3、互斥鎖是為上鎖而優化的;條件變量是為等待而優化的; 信號量既可用於上鎖,也可用於等待,因此會有更多的開銷和更高的復雜性。
4、互斥鎖,條件變量都只用於同一個進程的各線程間,而
信號量(有名信號量)可用於不同
進程間的同步。當信號量用於進程間同步時,要求信號量建立在共享內存區。
5、互斥量必須由同一線程獲取以及釋放,信號量和條件變量則可以由一個線程釋放,另一個線程得到。
6、
信號量的遞增和減少會被系統自動記住,系統內部的計數器實現信號量,不必擔心丟失,而喚醒一個條件變量時,如果沒有相應的線程在等待該條件變量,此次喚醒會被丟失。
5. 自旋鎖
自旋鎖與互斥量類似,但它不是通過休眠使進程阻塞,而是在獲取鎖之前一直處於忙等(自旋)阻塞狀態。自旋鎖可以用於以下情況:鎖被持有的時間短,而且線程並不希望在重新調度上花費太多的成本。
6. 屏障
屏障是指用戶可以協調多個線程並行工作的同步機制。屏障允許每個線程等待,直到所有的合作線程都到達某一點,然後從改點繼續執行。
還記pthread_join函數嗎?在子線程退出之前,主線程要一直等待。pthread_join函數就是一種屏障,它允許一個線程等待,直到另一個線程退出。屏障允許任意數量的線程等待,直到所有的線程完成處理工作,而線程不需要退出。所有線程達到屏障後可以接著工作。
如果我們要讓主線程在所有工作線程完成之後再做某項任務,一般把屏障計數值設為工作線程數加1,主線程也作為其中一個候選線程。
參考:
/content/7740733.html
http://blog.csdn.net/kobejayandy/article/details/18863543
https://www.douban.com/note/431142009/?type=like
/content/10291433.html
http://blog.chinaunix.net/space.php?uid=23061624&do=blog&cuid=2127853
http://www.360doc.com/content/12/0411/19/1317564_202838831.shtml
http://blog.chinaunix.net/uid-20671208-id-4935154.html
《unix環境高級編程》