欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
pthread的各種同步機制

簡(jiǎn)述


pthread是POSIX標準的多線(xiàn)程庫,UNIX、Linux上廣泛使用,windows上也有對應的實(shí)現,所有的函數都是pthread打頭,也就一百多個(gè)函數,不是很復雜。然而多線(xiàn)程編程被普遍認為復雜,主要是因為多線(xiàn)程給程序引入了一定的不可預知性,要控制這些不可預知性,就需要使用各種鎖各種同步機制,不同的情況就應該使用不同的鎖不同的機制。什么事情一旦放到多線(xiàn)程環(huán)境,要考慮的問(wèn)題立刻就上升了好幾個(gè)量級。多線(xiàn)程編程就像潘多拉魔盒,帶來(lái)的好處不可勝數,然而工程師只要一不小心,就很容易讓你的程序失去控制,所以你得用各種鎖各種機制管住它。要解決好這些問(wèn)題,工程師們就要充分了解這些鎖機制,分析不同的場(chǎng)景,選擇合適的解決方案。

處理方案不對的話(huà),那能不能正確跑完程序就只好看運氣啦~




預備


  1. 閱讀這篇文章之前你最好有一些實(shí)際的pthread使用經(jīng)驗,因為這篇文章不是寫(xiě)給從零開(kāi)始學(xué)習pthread的人的。
  2. 想要5分鐘內立刻搞定多線(xiàn)程同步機制的人,覺(jué)得文章太長(cháng)不看的人,這篇文章就不是寫(xiě)給你們看的。Mac請點(diǎn)左上角關(guān)閉,KDE和GNOME請點(diǎn)右上角關(guān)閉。
  3. 如果你經(jīng)常困惑于各種鎖和同步機制的方案,或者你想尋找比現有代碼更優(yōu)雅的方案來(lái)處理你遇到的多線(xiàn)程問(wèn)題,那這篇文章就是寫(xiě)給你的。
  4. 如果你發(fā)現別人的多線(xiàn)程代碼寫(xiě)得不對,但是勉強能跑,然后你找到他讓他改的時(shí)候,跟他解釋半天也不愿意改,那這篇文章就是寫(xiě)給他們的。




開(kāi)始。




Mutex Lock 互斥鎖


MUTual-EXclude Lock,互斥鎖。 它是理解最容易,使用最廣泛的一種同步機制。顧名思義,被這個(gè)鎖保護的臨界區就只允許一個(gè)線(xiàn)程進(jìn)入,其它線(xiàn)程如果沒(méi)有獲得鎖權限,那就只能在外面等著(zhù)。

它使用得非常廣泛,以至于大多數人談到鎖就是mutex。mutex是互斥鎖,pthread里面還有很多鎖,mutex只是其中一種。


相關(guān)宏和函數

    PTHREAD_MUTEX_INITIALIZER // 用于靜態(tài)的mutex的初始化,采用默認的attr。比如: static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); // 用于動(dòng)態(tài)的初始化    int pthread_mutex_destroy(pthread_mutex_t *mutex); // 把mutex鎖干掉,并且釋放所有它所占有的資源    int pthread_mutex_lock(pthread_mutex_t *mutex); // 請求鎖,如果當前mutex已經(jīng)被鎖,那么這個(gè)線(xiàn)程就會(huì )卡在這兒,直到mutex被釋放    int pthread_mutex_unlock(pthread_mutex_t *mutex); // 解鎖    int pthread_mutex_trylock(pthread_mutex_t *mutex); // 嘗試請求鎖,如果當前mutex已經(jīng)被鎖或者不可用,這個(gè)函數就直接return了,不會(huì )把線(xiàn)程卡住



要注意的地方


關(guān)于mutex的初始化


mutex的初始化分兩種,一種是用宏(PTHREAD_MUTEX_INITIALIZER),一種是用函數(pthread_mutex_init)。 如果沒(méi)有特殊的配置要求的話(huà),使用宏比較好,因為它比較快。只有真的需要配置的時(shí)候,才需要用函數。也就是說(shuō),凡是pthread_mutex_init(&mutex, NULL)的地方都可以使用PTHREAD_MUTEX_INITIALIZER,因為在pthread_mutex_init這個(gè)函數里的實(shí)現其實(shí)也是用了PTHREAD_MUTEX_INITIALIZER


    ///////////////////// pthread_src/include/pthread/pthread.h    #define PTHREAD_MUTEX_INITIALIZER __PTHREAD_MUTEX_INITIALIZER    ///////////////////// pthread_src/sysdeps/generic/bits/mutex.h    #  define __PTHREAD_MUTEX_INITIALIZER         { __PTHREAD_SPIN_LOCK_INITIALIZER, __PTHREAD_SPIN_LOCK_INITIALIZER, 0, 0, 0, 0, 0, 0 } // mutex鎖本質(zhì)上是一個(gè)spin lock,空轉鎖,關(guān)于空轉鎖的東西在下面會(huì )提到。    ///////////////////// pthread_src/sysdeps/generic/pt-mutex-init.c    int    _pthread_mutex_init (pthread_mutex_t *mutex,                 const pthread_mutexattr_t *attr)    {      *mutex = (pthread_mutex_t) __PTHREAD_MUTEX_INITIALIZER; // 你看,這里其實(shí)用的也是宏。就這一句是初始化,下面都是在設置屬性。      if (! attr          || memcmp (attr, &__pthread_default_mutexattr, sizeof (*attr) == 0))        /* The default attributes.  */        return 0;      if (! mutex->attr          || mutex->attr == __PTHREAD_ERRORCHECK_MUTEXATTR          || mutex->attr == __PTHREAD_RECURSIVE_MUTEXATTR)        mutex->attr = malloc (sizeof *attr);                //pthread_mutex_destroy釋放的就是這里的資源      if (! mutex->attr)        return ENOMEM;      *mutex->attr = *attr;      return 0;    }

但是業(yè)界有另一種說(shuō)法是:早年的POSIX只支持在static變量上使用PTHREAD_MUTEX_INITIALIZER,所以PTHREAD_MUTEX_INITIALIZER盡量不要到處都用,所以使用的時(shí)候你得搞清楚你的pthread的實(shí)現版本是不是比較老的。



mutex鎖不是萬(wàn)能靈藥


基本上所有的問(wèn)題都可以用互斥的方案去解決,大不了就是慢點(diǎn)兒,但不要不管什么情況都用互斥,都能采用這種方案不代表都適合采用這種方案。而且這里所說(shuō)的慢不是說(shuō)mutex的實(shí)現方案比較慢,而是互斥方案影響的面比較大,本來(lái)不需要通過(guò)互斥就能讓線(xiàn)程進(jìn)入臨界區,但用了互斥方案之后,就使這樣的線(xiàn)程不得不等待互斥鎖的釋放,所以就慢了。甚至有些場(chǎng)合用互斥就很蛋疼,比如多資源分配,線(xiàn)程步調通知等。 如果是讀多寫(xiě)少的場(chǎng)合,就比較適合讀寫(xiě)鎖(reader/writter lock),如果臨界區比較短,就適合空轉鎖(pin lock)...這些我在后面都會(huì )說(shuō)的,你可以翻到下面去看。

提到這個(gè)的原因是:大多數人學(xué)pthread學(xué)到mutex就結束了,然后不管什么都用mutex。那是不對的?。?!



預防死鎖


如果要進(jìn)入一段臨界區需要多個(gè)mutex鎖,那么就很容易導致死鎖,單個(gè)mutex鎖是不會(huì )引發(fā)死鎖的。要解決這個(gè)問(wèn)題也很簡(jiǎn)單,只要申請鎖的時(shí)候按照固定順序,或者及時(shí)釋放不需要的mutex鎖就可以。這就對我們的代碼有一定的要求,尤其是全局mutex鎖的時(shí)候,更需要遵守一個(gè)約定。

如果是全局mutex鎖,我習慣將它們寫(xiě)在同一個(gè)頭文件里。一個(gè)模塊的文件再多,都必須要有兩個(gè)umbrella header file。一個(gè)是整個(gè)模塊的傘,外界使用你的模塊的時(shí)候,只要include這個(gè)頭文件即可。另一個(gè)用于給模塊的所有子模塊去include,然后這個(gè)頭文件里面就放一些公用的宏啊,配置啊啥的,全局mutex放在這里就最合適了。這兩個(gè)文件不能是同一個(gè),否則容易出循環(huán)include的問(wèn)題。如果有人寫(xiě)模塊不喜歡寫(xiě)這樣的頭文件的,那現在就要改了。

然后我的mutex鎖的命名規則就是:作用_mutex_序號,比如LinkListMutex_mutex_1,OperationQueue_mutex_2,后面的序號在每次有新鎖的時(shí)候,就都加一個(gè)1。如果有哪個(gè)臨界區進(jìn)入的時(shí)候需要獲得多個(gè)mutex鎖的,我就按照序號的順序去進(jìn)行加鎖操作(pthread_mutex_lock),這樣就能夠保證不會(huì )出現死鎖了。



如果是屬于某個(gè)struct內部的mutex鎖,那么也一樣,只不過(guò)序號可以不必跟全局鎖掛鉤,也可以從1開(kāi)始數。



還有另一種方案也非常有效,就是用pthread_mutex_trylock函數來(lái)申請加鎖,這個(gè)函數在mutex鎖不可用時(shí),不像pthread_mutex_lock那樣會(huì )等待。pthread_mutex_trylock在申請加鎖失敗時(shí)立刻就會(huì )返回錯誤:EBUSY(鎖尚未解除)或者EINVAL(鎖變量不可用)。一旦在trylock的時(shí)候有錯誤返回,那就把前面已經(jīng)拿到的鎖全部釋放,然后過(guò)一段時(shí)間再來(lái)一遍。 當然也可以使用pthread_mutex_timedlock這個(gè)函數來(lái)申請加鎖,這個(gè)函數跟pthread_mutex_trylock類(lèi)似,不同的是,你可以傳入一個(gè)時(shí)間參數,在申請加鎖失敗之后會(huì )阻塞一段時(shí)間等解鎖,超時(shí)之后才返回錯誤。



這兩種方案我更多會(huì )使用第一種,原因如下:

  • 一般情況下進(jìn)入臨界區需要加的鎖數量不會(huì )太多,第一種方案能夠hold住。如果多于2個(gè),你就要考慮一下是否有些鎖是可以合并的了。

第一種方案適合鎖比較少的情況,因為這不會(huì )導致非常大的阻塞延時(shí)。但是當你要加的鎖非常多,ABCDE,你加到D的時(shí)候阻塞了,然而其他線(xiàn)程可能只需要AB就可以運行,就也會(huì )因為AB已經(jīng)被鎖住而阻塞,這時(shí)候才會(huì )采用第二種方案。如果要加的鎖本身就不多,只有AB兩個(gè),那么阻塞一下也還可以。

  • 第二種方案在面臨阻塞的時(shí)候,要操作的事情太多。

當你把所有的鎖都釋放以后,你的當前線(xiàn)程的處理策略就會(huì )導致你的代碼復雜度上升:當前線(xiàn)程總不能就此退出吧,你得找個(gè)地方把它放起來(lái),讓它去等待一段時(shí)間之后再去申請鎖,如果有多個(gè)線(xiàn)程出現了這樣的情況,你就需要一個(gè)線(xiàn)程池來(lái)存放這些等待解鎖的線(xiàn)程。如果臨界區是嵌套的,你在把這個(gè)線(xiàn)程掛起的時(shí)候,最好還要把外面的鎖也釋放掉,要不然也會(huì )容易導致死鎖,這就需要你在一個(gè)地方記錄當前線(xiàn)程使用鎖的情況。這里要做的事情太多,復雜度比較大,容易出錯。



所以總而言之,設計的時(shí)候盡量減少同一臨界區所需要mutex鎖的數量,然后采用第一種方案。如果確實(shí)有需求導致那么多mutex鎖,那么就只能采用第二種方案了,然后老老實(shí)實(shí)寫(xiě)好周邊代碼。但是!umbrella header file和按照序號命名mutex鎖是個(gè)非常好的習慣,可以允許你隨著(zhù)軟件的發(fā)展而靈活采取第一第二種方案。

但是到了semaphore情況下的死鎖處理方案時(shí),上面兩種方案就都不頂用了,后面我會(huì )說(shuō)。另外,還有一種死鎖是自己把自己鎖死了,這個(gè)我在后面也會(huì )說(shuō)。




Reader-Writter Lock 讀寫(xiě)鎖


前面mutex鎖有個(gè)缺點(diǎn),就是只要鎖住了,不管其他線(xiàn)程要干什么,都不允許進(jìn)入臨界區。設想這樣一種情況:臨界區foo變量在被bar1線(xiàn)程讀著(zhù),加了個(gè)mutex鎖,bar2線(xiàn)程如果也要讀foo變量,因為被bar1加了個(gè)互斥鎖,那就不能讀了。但事實(shí)情況是,讀取數據不影響數據內容本身,所以即便被1個(gè)線(xiàn)程讀著(zhù),另外一個(gè)線(xiàn)程也應該允許他去讀。除非另外一個(gè)線(xiàn)程是寫(xiě)操作,為了避免數據不一致的問(wèn)題,寫(xiě)線(xiàn)程就需要等讀線(xiàn)程都結束了再寫(xiě)。

因此誕生了Reader-Writter Lock,有的地方也叫Shared-Exclusive Lock,共享鎖。

Reader-Writter Lock的特性是這樣的,當一個(gè)線(xiàn)程加了讀鎖訪(fǎng)問(wèn)臨界區,另外一個(gè)線(xiàn)程也想訪(fǎng)問(wèn)臨界區讀取數據的時(shí)候,也可以加一個(gè)讀鎖,這樣另外一個(gè)線(xiàn)程就能夠成功進(jìn)入臨界區進(jìn)行讀操作了。此時(shí)讀鎖線(xiàn)程有兩個(gè)。當第三個(gè)線(xiàn)程需要進(jìn)行寫(xiě)操作時(shí),它需要加一個(gè)寫(xiě)鎖,這個(gè)寫(xiě)鎖只有在讀鎖的擁有者為0時(shí)才有效。也就是等前兩個(gè)讀線(xiàn)程都釋放讀鎖之后,第三個(gè)線(xiàn)程就能進(jìn)去寫(xiě)了??偨Y一下就是,讀寫(xiě)鎖里,讀鎖能允許多個(gè)線(xiàn)程同時(shí)去讀,但是寫(xiě)鎖在同一時(shí)刻只允許一個(gè)線(xiàn)程去寫(xiě)。

這樣更精細的控制,就能減少mutex導致的阻塞延遲時(shí)間。雖然用mutex也能起作用,但這種場(chǎng)合,明顯讀寫(xiě)鎖更好嘛!



相關(guān)宏和函數


    PTHREAD_RWLOCK_INITIALIZER    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);    int pthread_rwlock_timedrdlock_np(pthread_rwlock_t *rwlock, const struct timespec *deltatime); // 這個(gè)函數在Linux和Mac的man文檔里都沒(méi)有,新版的pthread.h里面也沒(méi)有,舊版的能找到    int pthread_rwlock_timedwrlock_np(pthread_rwlock_t *rwlock, const struct timespec *deltatime); // 同上



要注意的地方


命名


跟上面提到的寫(xiě)muetx鎖的約定一樣,操作,類(lèi)別,序號最好都要有。比如OperationQueue_rwlock_1。


認真區分使用場(chǎng)合,記得避免寫(xiě)線(xiàn)程饑餓


由于讀寫(xiě)鎖的性質(zhì),在默認情況下是很容易出現寫(xiě)線(xiàn)程饑餓的。因為它必須要等到所有讀鎖都釋放之后,才能成功申請寫(xiě)鎖。不過(guò)不同系統的實(shí)現版本對寫(xiě)線(xiàn)程的優(yōu)先級實(shí)現不同。Solaris下面就是寫(xiě)線(xiàn)程優(yōu)先,其他系統默認讀線(xiàn)程優(yōu)先。

比如在寫(xiě)線(xiàn)程阻塞的時(shí)候,有很多讀線(xiàn)程是可以一個(gè)接一個(gè)地在那兒插隊的(在默認情況下,只要有讀鎖在,寫(xiě)鎖就無(wú)法申請,然而讀鎖可以一直申請成功,就導致所謂的插隊現象),那么寫(xiě)線(xiàn)程就不知道什么時(shí)候才能申請成功寫(xiě)鎖了,然后它就餓死了。

為了控制寫(xiě)線(xiàn)程饑餓,必須要在創(chuàng )建讀寫(xiě)鎖的時(shí)候設置PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE,不要用PTHREAD_RWLOCK_PREFER_WRITER_NP啊,這個(gè)似乎沒(méi)什么用,感覺(jué)應該是個(gè)bug,不要問(wèn)我是怎么知道的。。。


////////////////////////////// /usr/include/pthread.h/* Read-write lock types.  */#if defined __USE_UNIX98 || defined __USE_XOPEN2Kenum{  PTHREAD_RWLOCK_PREFER_READER_NP,  PTHREAD_RWLOCK_PREFER_WRITER_NP, // 媽蛋,沒(méi)用,一樣reader優(yōu)先  PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP,  PTHREAD_RWLOCK_DEFAULT_NP = PTHREAD_RWLOCK_PREFER_READER_NP};

總的來(lái)說(shuō),這樣的鎖建立之后一定要設置優(yōu)先級,不然就容易出現寫(xiě)線(xiàn)程饑餓。而且讀寫(xiě)鎖適合讀多寫(xiě)少的情況,如果讀、寫(xiě)一樣多,那這時(shí)候還是用mutex鎖比較合理。




spin lock 空轉鎖


上面在給出mutex鎖的實(shí)現代碼的時(shí)候提到了這個(gè)spin lock,空轉鎖。它是互斥鎖、讀寫(xiě)鎖的基礎。在其它同步機制里condition variable、barrier等都有它的身影。

我先說(shuō)一下其他鎖申請加鎖的過(guò)程,你就知道什么是spin lock了。

互斥鎖和讀寫(xiě)鎖在申請加鎖的時(shí)候,會(huì )使得線(xiàn)程阻塞,阻塞的過(guò)程又分兩個(gè)階段,第一階段是會(huì )先空轉,可以理解成跑一個(gè)while循環(huán),不斷地去申請鎖,在空轉一定時(shí)間之后,線(xiàn)程會(huì )進(jìn)入waiting狀態(tài)(對的,跟進(jìn)程一樣,線(xiàn)程也分很多狀態(tài)),此時(shí)線(xiàn)程就不占用CPU資源了,等鎖可用的時(shí)候,這個(gè)線(xiàn)程會(huì )被喚醒。

為什么會(huì )有這兩個(gè)階段呢?主要還是出于效率因素。


  • 如果單純在申請鎖失敗之后,立刻將線(xiàn)程狀態(tài)掛起,會(huì )帶來(lái)context切換的開(kāi)銷(xiāo),但掛起之后就可以不占用CPU資源了,原屬于這個(gè)線(xiàn)程的CPU時(shí)間就可以拿去做更加有意義的事情。假設鎖在第一次申請失敗之后就又可用了,那么短時(shí)間內進(jìn)行context切換的開(kāi)銷(xiāo)就顯得很沒(méi)效率。

  • 如果單純在申請鎖失敗之后,不斷輪詢(xún)申請加鎖,那么可以在第一時(shí)間申請加鎖成功,同時(shí)避免了context切換的開(kāi)銷(xiāo),但是浪費了寶貴的CPU時(shí)間。假設鎖在第一次申請失敗之后,很久很久才能可用,那么CPU在這么長(cháng)時(shí)間里都被這個(gè)線(xiàn)程拿來(lái)輪詢(xún)了,也顯得很沒(méi)效率。


于是就出現了兩種方案結合的情況:在第一次申請加鎖失敗的時(shí)候,先不著(zhù)急切換context,空轉一段時(shí)間。如果鎖在短時(shí)間內又可用了,那么就避免了context切換的開(kāi)銷(xiāo),CPU浪費的時(shí)間也不多??辙D一段時(shí)間之后發(fā)現還是不能申請加鎖成功,那么就有很大概率在將來(lái)的不短的一段時(shí)間里面加鎖也不成功,那么就把線(xiàn)程掛起,把輪詢(xún)用的CPU時(shí)間釋放出來(lái)給別的地方用。

所以spin lock就是這樣的一個(gè)鎖:它在第一次申請加鎖失敗的時(shí)候,會(huì )不斷輪詢(xún),直到申請加鎖成功為止,期間不會(huì )進(jìn)行線(xiàn)程context的切換。互斥鎖和讀寫(xiě)鎖基于spin lock又多做了超時(shí)檢查和切換context的操作,如此而已。


這里是spin lock申請加鎖的實(shí)現:


/////////////////////////////pthread_src/sysdeps/posix/pt-spin.c/* Lock the spin lock object LOCK.  If the lock is held by another    thread spin until it becomes available.  */int_pthread_spin_lock (__pthread_spinlock_t *lock){  int i;  while (1)    {      for (i = 0; i < __pthread_spin_count; i++)    {      if (__pthread_spin_trylock (lock) == 0)        return 0;    }      __sched_yield ();    }}



相關(guān)宏和函數


我沒(méi)在man里面找到spin lock相關(guān)的函數,但事實(shí)上外面還是能夠使用的,下面是我在源代碼里面挖到的原型:


////////////////////////////////pthread_src/pthread/pt-spin-inlines.c/* Weak aliases for the spin lock functions.  Note that   pthread_spin_lock is left out deliberately.  We already provide an   implementation for it in pt-spin.c.  */weak_alias (__pthread_spin_destroy, pthread_spin_destroy);weak_alias (__pthread_spin_init, pthread_spin_init);weak_alias (__pthread_spin_trylock, pthread_spin_trylock);weak_alias (__pthread_spin_unlock, pthread_spin_unlock);/////////////////////////////////pthread_src/sysdeps/posix/pt-spin.cweak_alias (_pthread_spin_lock, pthread_spin_lock);/*-------------------------------------------------*/    PTHREAD_SPINLOCK_INITIALIZER    int pthread_spin_init (__pthread_spinlock_t *__lock, int __pshared);    int pthread_spin_destroy (__pthread_spinlock_t *__lock);    int pthread_spin_trylock (__pthread_spinlock_t *__lock);    int pthread_spin_unlock (__pthread_spinlock_t *__lock);    int pthread_spin_lock (__pthread_spinlock_t *__lock);/*-------------------------------------------------*/



注意事項



還是要分清楚使用場(chǎng)合


了解了空轉鎖的特性,我們就發(fā)現這個(gè)鎖其實(shí)非常適合臨界區非常短的場(chǎng)合,或者實(shí)時(shí)性要求比較高的場(chǎng)合。

由于臨界區短,線(xiàn)程需要等待的時(shí)間也短,即便輪詢(xún)浪費CPU資源,也浪費不了多少,還省了context切換的開(kāi)銷(xiāo)。 由于實(shí)時(shí)性要求比較高,來(lái)不及等待context切換的時(shí)間,那就只能浪費CPU資源在那兒輪詢(xún)了。

不過(guò)說(shuō)實(shí)話(huà),大部分情況你都不會(huì )直接用到空轉鎖,其他鎖在申請不到加鎖時(shí)也是會(huì )空轉一定時(shí)間的,如果連這段時(shí)間都無(wú)法滿(mǎn)足你的請求,那要么就是你扔的線(xiàn)程太多,或者你的臨界區沒(méi)你想象的那么短。




pthread_cleanup_push() & pthread_cleanup_pop()


線(xiàn)程是允許在退出的時(shí)候,調用一些回調方法的。如果你需要做類(lèi)似的事情,那么就用以下這兩種方法:


    void pthread_cleanup_push(void (*callback)(void *), void *arg);    void pthread_cleanup_pop(int execute);

正如名字所暗示的,它背后有一個(gè)stack,你可以塞很多個(gè)callback函數進(jìn)去,然后調用的時(shí)候按照先入后出的順序調用這些callback。所以你在塞callback的時(shí)候,如果是關(guān)心調用順序的,那就得注意這一點(diǎn)了。

但是!你塞進(jìn)去的callback只有在以下情況下才會(huì )被調用:


  1. 線(xiàn)程通過(guò)pthread_exit()函數退出
  2. 線(xiàn)程被pthread_cancel()取消
  3. pthread_cleanup_pop(int execute)時(shí),execute傳了一個(gè)非0值


也就是說(shuō),如果你的線(xiàn)程函數是這么寫(xiě)的,那在線(xiàn)程結束的時(shí)候就不會(huì )調到你塞進(jìn)去的那些callback了:


static void * thread_function(void *args){    ...    ...    ...    ...    return 0; // 線(xiàn)程退出時(shí)沒(méi)有調用pthread_exit()退出,而是直接return,此時(shí)是不會(huì )調用棧內callback的}


exit()行不行?尼瑪一調用這個(gè)整個(gè)進(jìn)程就掛掉了~只要在任意線(xiàn)程調用exit(),整個(gè)進(jìn)程就結束了,不要瞎搞。pthread_cleanup_push塞入的callback可以用來(lái)記錄線(xiàn)程結束的點(diǎn),活著(zhù)打打日志啥的,一般不太會(huì )在這里執行業(yè)務(wù)邏輯。在線(xiàn)程結束之后如果要執行業(yè)務(wù)邏輯,一般用下面提到的pthread_join。



注意事項

callback函數是可以傳參數的


對的,在pthread_cleanup_push函數中,第二個(gè)參數的值會(huì )作為callback函數的第一個(gè)參數,不要浪費了,拿來(lái)打打日志也不錯。舉個(gè)例子:


void callback(void *callback_arg){    printf("arg is : %s\n", (char *)callback_arg);}static void * thread_function(void *thread_arg){    ...    pthread_cleanup_push(callback, "this is a queue thread, and was terminated.");    ...    pthread_exit((void *) 0); // 這句不調用,線(xiàn)程結束就不會(huì )調用你塞進(jìn)去的callback函數。    return ((void *) 0);}int main (){    ...    ...    error = pthread_create(&tid, NULL, thread_function, (void *)thread_arg)    ...    ...    return 0;}


你也發(fā)現了,callback函數的參數是在線(xiàn)程函數里面設置的,所以拿來(lái)做業(yè)務(wù)也是可以的,不過(guò)一般都是拿來(lái)做清理的事情,很少會(huì )把它放到業(yè)務(wù)里面去做。



要保持callback棧平衡


有的時(shí)候我們并不一定要在線(xiàn)程結束的時(shí)候調用這些callback,那怎么辦?直接return不就好了么,return的話(huà),不就不調用callback了?

如果你真是這么想的,請去撞墻5分鐘。

callback的調用棧一定要保持平衡,如果你不保持平衡就退出了線(xiàn)程,后面的結果是undefine的,有的系統就core dump了(比如Mac),有的系統還就這么跑過(guò)去了一點(diǎn)反應也沒(méi)有(這個(gè)是我猜的,沒(méi)驗證過(guò),因為callback棧不平衡的結果是未定義的)。

所以遇到有時(shí)要調用有時(shí)又不需要的時(shí)候,這么寫(xiě)才是正確的姿勢:


void callback1(void *callback_arg){    printf("arg is : %s\n", (char *)callback_arg);}void callback2(void *callback_arg){    printf("arg is : %s\n", (char *)callback_arg);}static void * thread_function(void *thread_arg){    ...    pthread_cleanup_push(callback1, "this is callback 1.");    pthread_cleanup_push(callback2, "this is callback 2.");    ...    if (thread_arg->should_callback) {        pthread_exit((void *) result);    }    pthread_cleanup_pop(0); // 傳遞參數0,在pop的時(shí)候就不會(huì )調用對應的callback,如果傳遞非0值,pop的時(shí)候就會(huì )調用對應callback了。    pthread_cleanup_pop(0); // push了兩次就pop兩次,你要是只pop一次也可以,因為下面也有pthread_exit。這樣一來(lái)就只會(huì )調用callback1,不會(huì )調用callback2了,因為callback2在棧頂被上面那句pop出去了。    pthread_exit((void *) result);   // 所有的線(xiàn)程都應該用pthread_exit來(lái)結束,一方面是確保棧平衡,另一方面,也給別的線(xiàn)程join提供了方便    return ((void *) 0);}int main (){    ...    ...    error = pthread_create(&tid, NULL, thread_function, (void *)thread_arg)    ...    ...    return 0;}




pthread_join()


在線(xiàn)程結束的時(shí)候,我們能通過(guò)上面的pthread_cleanup_push塞入的callback方法知道,也能通過(guò)pthread_join這個(gè)方法知道。一般情況下,如果是出于業(yè)務(wù)的需要要知道線(xiàn)程何時(shí)結束的,都會(huì )采用pthread_join這個(gè)方法。


它適用這樣的場(chǎng)景:

你有兩個(gè)線(xiàn)程,B線(xiàn)程在做某些事情之前,必須要等待A線(xiàn)程把事情做完,然后才能接著(zhù)做下去。這時(shí)候就可以用join。


原型:


    int pthread_join(pthread_t thread, void **value_ptr);


在B線(xiàn)程里調用這個(gè)方法,第一個(gè)參數傳A線(xiàn)程的thread_id, 第二個(gè)參數你可以扔一個(gè)指針進(jìn)去。當A線(xiàn)程調用pthread_exit(void *value_ptr)來(lái)結束的時(shí)候,A的value_ptr就會(huì )到pthread_joinvalue_ptr去,你可以理解成A把它計算出來(lái)的結果放到exit函數里面去,然后其他join的線(xiàn)程就能拿到這個(gè)數據了。

在B線(xiàn)程join了A線(xiàn)程之后,B線(xiàn)程會(huì )阻塞住,直到A線(xiàn)程跑完。A線(xiàn)程跑完之后,自動(dòng)被detach,后續再要join的線(xiàn)程就會(huì )報EINVAL。



注意事項


新創(chuàng )建的線(xiàn)程默認是join屬性,每一個(gè)join屬性的線(xiàn)程都需要通過(guò)pthread_join來(lái)回收資源


  • 如果A線(xiàn)程已經(jīng)跑完,但沒(méi)被join過(guò),此時(shí)B線(xiàn)程要去join A線(xiàn)程的時(shí)候,pthread_join是會(huì )立刻正確返回的,之后A線(xiàn)程就被detach了,占用的資源也會(huì )被釋放。
  • 如果A線(xiàn)程已經(jīng)跑完,后面沒(méi)人join它,它占用的資源就會(huì )一直在哪兒,變成僵尸線(xiàn)程。

所以要么在創(chuàng )建線(xiàn)程的時(shí)候就把線(xiàn)程設置為detach的線(xiàn)程,這樣線(xiàn)程跑完以后不用join,占用的資源自動(dòng)回收。

要么不要忘記去join一下,把資源回收了,不要留僵尸。


注意傳遞的參數的內存生命周期


雖然線(xiàn)程和進(jìn)程共享同一個(gè)進(jìn)程資源,但如果在pthread_exit()里面你傳遞的指針指向的是棧內存,那么在結束之后,這片內存還是會(huì )被回收的,具體到使用的時(shí)候,不同的系統又是不同的方案了。

還有就是,一定要在獲得value_ptr之后,檢查一下value_ptr是否PTHREAD_CANCELED,因為如果你要等待的線(xiàn)程被cancel掉了,你拿到的就是這個(gè)數據。


多個(gè)線(xiàn)程join同一個(gè)線(xiàn)程


pthread_join是允許多個(gè)線(xiàn)程等待同一個(gè)線(xiàn)程的結束的。如果要一個(gè)線(xiàn)程等待多個(gè)線(xiàn)程的結束,那就需要用下面提到的條件變量了,或者barrier也行。

但是多個(gè)線(xiàn)程join同一個(gè)線(xiàn)程的時(shí)候,情況就比較多。多而已,不復雜。我們先建立一個(gè)約定:A線(xiàn)程是要被join的線(xiàn)程,BCDEF是要等待A線(xiàn)程結束的線(xiàn)程。下面說(shuō)一下每種情況:


  • A線(xiàn)程正在運行,BCDEF線(xiàn)程發(fā)起對A的join,發(fā)起join結束后,A仍然在運行中

此時(shí)BCDEF線(xiàn)程都會(huì )被阻塞,等待A線(xiàn)程的結束。A線(xiàn)程結束之后,BCDEF都被喚醒,能夠正常獲得A線(xiàn)程通過(guò)pthread_exit()返回的數據。


  • A線(xiàn)程正在運行,BCDEF發(fā)起對A的join,BCD發(fā)起join成功后,A線(xiàn)程結束,然后EF發(fā)起join

此時(shí)BCD線(xiàn)程能夠正常被喚醒,并完成任務(wù),由于被join后A線(xiàn)程被detach,資源釋放,后續EF再要發(fā)起join,就會(huì )EINVAL。


  • A線(xiàn)程正在運行,且運行結束。此時(shí)BCDEF發(fā)起對A的join。

此時(shí)誰(shuí)先調用成功,誰(shuí)就能完成任務(wù),后續再要join的就都會(huì )EINVAL。一旦有一個(gè)線(xiàn)程join成功,A立刻被detach,資源釋放,然后后面其他的線(xiàn)程就都不會(huì )join成功。



總的來(lái)說(shuō),只要線(xiàn)程運行結束,并且被detach了,后面再join就不行了,只要線(xiàn)程還在運行中,就能join。如果運行結束了,第一次被join之后,線(xiàn)程就被detach了,后續就不能join。當然了,如果線(xiàn)程本來(lái)就是detach屬性的線(xiàn)程,那任何時(shí)候都無(wú)法被join。




Condition Variables 條件變量


pthread_join解決的是多個(gè)線(xiàn)程等待同一個(gè)線(xiàn)程的結束。條件變量能在合適的時(shí)候喚醒正在等待的線(xiàn)程。具體什么時(shí)候合適由你自己決定。它必須要跟互斥鎖聯(lián)合起來(lái)用。原因我會(huì )在注意事項里面講。

場(chǎng)景:B線(xiàn)程和A線(xiàn)程之間有合作關(guān)系,當A線(xiàn)程完成某件事情之前,B線(xiàn)程會(huì )等待。當A線(xiàn)程完成某件事情之后,需要讓B線(xiàn)程知道,然后B線(xiàn)程從等待狀態(tài)中被喚醒,然后繼續做自己要做的事情。

如果不用條件變量的話(huà),也行。那就是搞個(gè)volatile變量,然后讓其他線(xiàn)程不斷輪詢(xún),一旦這個(gè)變量到了某個(gè)值,你就可以讓線(xiàn)程繼續了。如果有多個(gè)線(xiàn)程需要修改這個(gè)變量,那就再加個(gè)互斥鎖或者讀寫(xiě)鎖。

但是?。?!這做法太特么愚蠢了,還特別浪費CPU時(shí)間,所以還在用volatile變量標記線(xiàn)程狀態(tài)的你們也真是夠了?。?!

大致的實(shí)現原理是:一個(gè)條件變量背后有一個(gè)池子,所有需要wait這個(gè)變量的線(xiàn)程都會(huì )進(jìn)入這個(gè)池子。當有線(xiàn)程扔出這個(gè)條件變量的signal,系統就會(huì )把這個(gè)池子里面的線(xiàn)程挨個(gè)喚醒。



相關(guān)宏和函數


    PTHREAD_COND_INITIALIZER    int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);    int pthread_cond_destroy(pthread_cond_t *cond);    int pthread_cond_signal(pthread_cond_t *cond);    int pthread_cond_broadcast(pthread_cond_t *cond);    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);


補充一下,原則上pthread_cond_signal是只通知一個(gè)線(xiàn)程,pthread_cond_broadcast是用于通知很多線(xiàn)程。但POSIX標準也允許讓pthread_cond_signal用于通知多個(gè)線(xiàn)程,不強制要求只允許通知一個(gè)線(xiàn)程。具體看各系統的實(shí)現。一般我都是用pthread_cond_broadcast。

另外,在調用pthread_cond_wait之前,必須要申請互斥鎖,當線(xiàn)程通過(guò)pthread_cond_wait進(jìn)入waiting狀態(tài)時(shí),會(huì )釋放傳入的互斥鎖。

下面我先給一個(gè)條件變量的使用例子,然后再講需要注意的點(diǎn)。


void thread_waiting_for_condition_signal (){    pthread_mutex_lock(&mutex);    while (operation_queue == NULL) {        pthread_cond_wait(&condition_variable_signal, &mutex);    }    /*********************************/    /* 做一些關(guān)于operation_queue的事 */    /*********************************/    pthread_mutex_unlock(&mutex);}void thread_prepare_queue (){    pthread_mutex_lock(&mutex);    /*********************************/    /* 做一些關(guān)于operation_queue的事 */    /*********************************/    pthread_cond_signal(&condition_variable_signal); // 事情做完了之后要扔信號給等待的線(xiàn)程告訴他們做完了    pthread_mutex_unlock(&mutex);    /**************************/    /* 這里可以做一些別的事情 */    /**************************/    ...    pthread_exit((void *) 0);}



要注意的地方


一定要跟mutex配合使用


    void thread_function_1 ()    {        done = 1;        pthread_cond_signal(&condition_variable_signal);    }    void thread_function_2 ()    {        while (done == 0) {            pthread_cond_wait(&condition_variable_signal, NULL);        }    }


這樣行不行?當然不行。為什么不行?


這里涉及一個(gè)非常精巧的情況:在thread_function_2發(fā)現done=0的時(shí)候,準備要進(jìn)行下一步的wait操作。在具體開(kāi)始下一步的wait操作之前,thread_function_1一口氣完成了設置done,發(fā)送信號的事情。嗯,thread_function_2還沒(méi)來(lái)得及waiting呢,thread_function_1就把信號發(fā)出去了,也沒(méi)人接收這信號,thread_function_2繼續執行waiting之后,就只能等待多戈了。



一定要檢測你要操作的內容


    void thread_function_1 ()    {        pthread_mutex_lock(&mutex);        ...        operation_queue = create_operation_queue();        ...        pthread_cond_signal(&condition_variable_signal);        pthread_mutex_unlock(&mutex);    }    void thread_function_2 ()    {        pthread_mutex_lock(&mutex);        ...        pthread_cond_wait(&condition_variable_signal, &mutex);        ...        pthread_mutex_unlock(&mutex);    }


這樣行不行?當然不行。為什么不行?


比如thread_function_1一下子就跑完了,operation_queue也初始化好了,信號也扔出去了。這時(shí)候thread_function_2剛剛啟動(dòng),由于它沒(méi)有去先看一下operation_queue是否可用,直接就進(jìn)入waiting狀態(tài)。然而事實(shí)是operation_queue早已搞定,再也不會(huì )有人扔我已經(jīng)搞定operation_queue啦的信號,thread_function_2也不知道operation_queue已經(jīng)好了,就只能一直在那兒等待多戈了...



一定要用while來(lái)檢測你要操作的內容而不是if


    void thread_function_1 ()    {        pthread_mutex_lock(&mutex);        done = 1;        pthread_cond_signal(&condition_variable_signal);        pthread_mutex_unlock(&mutex);    }    void thread_function_2 ()    {        pthread_mutex_lock(&mutex);        if (done == 0) {            pthread_cond_wait(&condition_variable_signal, &mutex);        }        pthread_mutex_unlock(&mutex);    }


這樣行不行?大多數情況行,但是用while更加安全。


如果有別人寫(xiě)一個(gè)線(xiàn)程去把這個(gè)done搞成0了,期間沒(méi)有申請mutex鎖。

那么這時(shí)用if去判斷的話(huà),由于線(xiàn)程已經(jīng)從wait狀態(tài)喚醒,它會(huì )直接做下面的事情,而全然不知done的值已經(jīng)變了。

如果這時(shí)用while去判斷的話(huà),在pthread_cond_wait解除wait狀態(tài)之后,會(huì )再去while那邊判斷一次done的值,只有這次done的值對了,才不會(huì )進(jìn)入wait。如果這期間done被別的不長(cháng)眼的線(xiàn)程給改了,while補充的那一次判斷就幫了你一把,能繼續進(jìn)入waiting。

不過(guò)這解決不了根本問(wèn)題哈,如果那個(gè)不長(cháng)眼的線(xiàn)程在while的第二次判斷之后改了done,那還是要悲劇。根本方案還是要在改done的時(shí)候加mutex鎖。

總而言之,用if也可以,畢竟不太容易出現不長(cháng)眼的線(xiàn)程改done變量不申請加mutex鎖的。用while的話(huà)就多了一次判斷,安全了一點(diǎn),即便有不長(cháng)眼的線(xiàn)程干了這么齷齪的事情,也還能hold住。



扔信號的時(shí)候,在臨界區里面扔,不要在臨界區外扔


    void thread_function_1 ()    {        pthread_mutex_lock(&mutex);        done = 1;        pthread_mutex_unlock(&mutex);        pthread_cond_signal(&condition_variable_signal);    }    void thread_function_2 ()    {        pthread_mutex_lock(&mutex);        if (done == 0) {            pthread_cond_wait(&condition_variable_signal, &mutex);        }        pthread_mutex_unlock(&mutex);    }


這樣行不行?當然不行。為什么不行?《Advanced Programming in the UNIX Enviroment 3 Edtion》這本書(shū)里也把扔信號的事兒放在臨界區外面了呢。


不行就是不行,哪怕是圣經(jīng)上這么寫(xiě),也不行。哼。


就應該永遠都在臨界區里面扔條件信號,我畫(huà)了一個(gè)高清圖來(lái)解釋這個(gè)事情,圖比較大,可能要加載一段時(shí)間:

 
 

看到了吧,1的情況就是在臨界區外扔信號的壞處。由于在臨界區外,其他線(xiàn)程要申請加mutex鎖是可以成功的,然后這個(gè)線(xiàn)程要是改了你的敏感數據,你就只能去哭了...




semaphore 信號量


pthread庫里面藏了一個(gè)semaphore,man手冊里面似乎也找不到semaphore相關(guān)的函數。

semaphore事實(shí)上就是我們學(xué)《操作系統》的時(shí)候所說(shuō)的PV操作。 你也可以把它理解成帶有數量控制的互斥鎖,當sem_init(&sem, 0, 1);時(shí),他就是一個(gè)mutex鎖了。


場(chǎng)景:比如有3臺打印機,有5個(gè)線(xiàn)程要使用打印機,那么semaphore就會(huì )先記錄好有3臺,每成功被申請一次,就減1,減到0時(shí),后面的申請就會(huì )被拒絕。


它也可以用mutex和條件變量來(lái)實(shí)現,但實(shí)際上還是用semaphore比較方便。



相關(guān)函數


    int sem_destroy(sem_t *sem);    int sem_init(sem_t *sem, int pshared, unsigned int value); // 里的第二個(gè)參數是表示這個(gè)semaphore是否跨進(jìn)程可見(jiàn),Linux下還不支持這個(gè),所以這個(gè)參數一般都傳0    int sem_wait(sem_t *sem); // 如果sempahore的數值還夠,那就semaphore數值減1,然后進(jìn)入臨界區。也就是傳說(shuō)中的P操作。    int sem_post(sem_t *sem); // 這個(gè)函數會(huì )給semphore的值加1,也就是V操作。    int sem_getvalue(sem_t *sem, int *valp); // 注意了,它把semaphore的值通過(guò)你傳進(jìn)去的指針告訴你,而不是用這個(gè)函數的返回值告訴你。


要注意的地方


semaphore下的死鎖


mutex下的死鎖比較好處理,因為mutex只會(huì )鎖一個(gè)資源(當semaphore的值為1時(shí),就是個(gè)mutex鎖),按照順序來(lái)申請mutex鎖就好了。但是到了semaphore這里,由于資源數量不止1個(gè),死鎖情況就顯得比較復雜。

要想避免死鎖,即便采用前面提到的方案:按照順序加鎖,一旦出現加鎖失敗,就釋放所有資源。這招也行不通。假設這樣一個(gè)情況,當前系統剩余資源情況如下:


剩余資源:      全部系統資源A:3             A:10B:2             B:10C:4             C:10


此時(shí)有兩個(gè)線(xiàn)程:t1, t2。

t1需要3個(gè)A,4個(gè)B,1個(gè)Ct2需要2個(gè)A,2個(gè)B,2個(gè)C  根據當前剩余資源列表來(lái)看,t2可以得到執行,不會(huì )出現死鎖。


假設我們采用舊方案:順序申請加鎖,加鎖失敗就釋放。我們按照CPU時(shí)間來(lái)推演一個(gè):

    1. t1申請3個(gè)A -> 成功    2. t2申請2個(gè)A -> 失敗,等待    3. t1申請4個(gè)B -> 失敗,等待,并釋放3個(gè)A    4. t1申請3個(gè)A -> 成功    5. t2申請2個(gè)A -> 失敗,等待    6. t1申請4個(gè)B -> 失敗,等待,并釋放3個(gè)A    7. t1申請3個(gè)A -> 成功    8. t2申請2個(gè)A -> 失敗,等待    9. t1申請4個(gè)B -> 失敗,等待,并釋放3個(gè)A    ...


發(fā)現沒(méi)有,這時(shí)候t1和t2都得不到執行,但實(shí)際上系統的剩余資源是滿(mǎn)足t2的要求的,但由于運氣不好,搶資源搶不過(guò)t1,在有新的資源被釋放之前,這倆線(xiàn)程就一直在那兒搶來(lái)?yè)屓サ貌坏綀绦辛恕?/p>

要解決這樣的問(wèn)題,就需要采用銀行家算法,銀行家算法描述起來(lái)很簡(jiǎn)單:獲取所有候選線(xiàn)程的需求,隨機取一個(gè)假設資源分配出去,看是否能夠分配成功。如果不能,就換一個(gè)候選者,再進(jìn)行資源分配。直到有線(xiàn)程滿(mǎn)足需求為止。如果都不能滿(mǎn)足,那就掛起所有線(xiàn)程,等待新的資源釋放。

也就是可以理解成很多個(gè)人去貸款,銀行家先假設你們都能按期還得起錢(qián),按照你們的需求給你們派錢(qián),不過(guò)這不是真的派出去了,只是先寫(xiě)在紙上,銀行家一推算,臥槽,到后面會(huì )出現資金缺口,那就換一種派發(fā)方式,直到?jīng)]有資金缺口為止。

說(shuō)白了,你需要在你的程序里面建立一個(gè)資源分配者的角色,所有待分配資源的線(xiàn)程都去一個(gè)池子里排隊,然后這個(gè)資源分配者一次只允許一個(gè)線(xiàn)程來(lái)請求資源,如果請求失敗,就換下一個(gè),如果池子里沒(méi)有線(xiàn)程能夠被滿(mǎn)足需求,那就集體掛起,然后等有新的資源來(lái)了,就再把這些線(xiàn)程一個(gè)一個(gè)叫過(guò)來(lái)進(jìn)行資源分配。




Barriers


Barrier可以理解成一個(gè)mile stone。當一個(gè)線(xiàn)程率先跑到mile stone的時(shí)候,就先等待。當其他線(xiàn)程都到位之后,再從等待狀態(tài)喚醒,繼續做后面的事情。

場(chǎng)景:超大數組排序的時(shí)候,可以采用多線(xiàn)程的方案來(lái)排序。比如開(kāi)10個(gè)線(xiàn)程分別排這個(gè)超大數組的10個(gè)部分。必須要這10個(gè)線(xiàn)程都完成了各自的排序,你才能進(jìn)行后續的歸并操作。先完成的線(xiàn)程會(huì )掛起等待,直到所有線(xiàn)程都完成之后,才喚醒所有等待的線(xiàn)程。

前面有提到過(guò)條件變量pthread_join,前者是在做完某件事情通知其他線(xiàn)程,后者是在線(xiàn)程結束之后讓其他線(xiàn)程能夠獲得執行結果。如果有多個(gè)線(xiàn)程同時(shí)做一件事情,用上面這兩者可以有次序地進(jìn)行同步。另外,用semaphore也可以實(shí)現Barrier的功能。

但是我們已經(jīng)有Barrier了好嗎!你們能不要把代碼搞那么復雜嗎!



相關(guān)宏和函數


int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *restrict attr, unsigned count);int pthread_barrier_destroy(pthread_barrier_t *barrier);int pthread_barrier_wait(pthread_barrier_t *barrier);


pthread_barrier_wait在喚醒之后,會(huì )有兩種返回值:0或者PTHREAD_BARRIER_SERIAL_THREAD,在眾多線(xiàn)程中只會(huì )有一個(gè)線(xiàn)程在喚醒時(shí)得到PTHREAD_BARRIER_SERIAL_THREAD的返回,其他返回都是0。拿到PTHREAD_BARRIER_SERIAL_THREAD返回的,表示這是上天選擇的主線(xiàn)程~


要注意的地方


其實(shí)Barrier很少被錯用,因為本來(lái)也沒(méi)幾個(gè)函數。更多的情況是有人不知道有Barrier,然后用其他的方式實(shí)現了類(lèi)似Barrier的功能。知道就好了。




關(guān)于attr



thread


創(chuàng )建thread的時(shí)候是可以設置attr的:detachstate、guardsize、stackaddr、stacksize。一般情況下我都是采取默認的設置。只有在我非常確定這個(gè)線(xiàn)程不需要跟其他線(xiàn)程協(xié)作的時(shí)候,我會(huì )把detachstate設置為PTHREAD_CREATE_DETACHED。



mutex


創(chuàng )建mutex的時(shí)候也是可以設置attr的:process-shared、robust、type。一般情況下盡量不要出現跨進(jìn)程的鎖共享,萬(wàn)一有個(gè)相關(guān)進(jìn)程被酒殺(kill 9)了,而且死之前它抱著(zhù)鎖沒(méi)放,你后面的事情就麻煩了,基本無(wú)解。process-sharedrobust就是跟跨進(jìn)程有關(guān)。

關(guān)于type,我強烈建議顯式設置為PTHREAD_MUTEX_ERRORCHECK。在Linux下,默認的typePTHREAD_MUTEX_NORMAL。這在下面這種情況下會(huì )導致死鎖:


void thread_function(){    pthread_mutex_lock(&mutex);    foo();    pthread_mutex_unlock(&mutex);}void foo(){    pthread_mutex_lock(&mutex);    pthread_mutex_unlock(&mutex);}


上面的代碼看著(zhù)很正常是吧?但由于在調用foo之前,mutex已經(jīng)被鎖住了,于是foo就停在那邊等待thread_function釋放mutex。但是!thread_function必須要等foo跑完才能解鎖,然后現在foo被卡住了。。。

如果type設置為PTHREAD_MUTEX_ERRORCHECK,那在foo里面的pthread_mutex_lock就會(huì )返回EDEADLK。如果你要求執行foo的時(shí)候一定要處于mutex的臨界區,那就要這么判斷。

如果type設置為PTHREAD_MUTEX_RECURSIVE,也不會(huì )產(chǎn)生死鎖,但不建議用這個(gè)。PTHREAD_MUTEX_RECURSIVE使用的場(chǎng)景其實(shí)很少,我一時(shí)半會(huì )兒也想不到哪個(gè)場(chǎng)景不得不采用PTHREAD_MUTEX_RECURSIVE。

嗯,其他應該沒(méi)什么了吧。




總結


這篇文章主要講了pthread的各種同步機制相關(guān)的東西:mutex、reader-writter、spin、cleanup callbacks、join、condition variable、semaphore、barrier。其中cleanup callbacks不算是同步機制,但是我看到也有人拿這個(gè)作為同步機制的一部分寫(xiě)在程序中,這是不對的!所以我才寫(xiě)了一下這個(gè)。

文章很長(cháng),相信你們看到這里也不容易,看完了這篇文章,你對多線(xiàn)程編程的同步機制應該可以說(shuō)比較了解了。但我還要說(shuō)的是,多線(xiàn)程編程的復雜點(diǎn)不僅僅在于同步機制,例如多線(xiàn)程跟系統信號的協(xié)作、多線(xiàn)程創(chuàng )建進(jìn)程后的協(xié)作和控制、多線(xiàn)程和I/O之間的協(xié)作和控制、函數的可重入性等,我看我什么時(shí)候有時(shí)間再寫(xiě)這些內容了。

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
【轉】pthread多線(xiàn)程編程整理 - 與時(shí)間賽跑的使者
實(shí)現線(xiàn)程讀寫(xiě)鎖的四種方法
Linux 多線(xiàn)程和鎖
LINUX 多線(xiàn)程互斥量和讀寫(xiě)鎖區別
[轉]linux-多線(xiàn)程
linux c 線(xiàn)程間同步(通信)的幾種方法
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久