APUE第11章 線程

10.1 引言

pthread

在前面的章節(jié)中討論了進程第步,學習了UNIX進程的環(huán)境、進程間的 關(guān)系以及控制進程的不同方式赊抖∷野可以看到在相關(guān)的進程間可以存在一定 的共享的猛。
本章將進一步深入理解進程,了解如何使用多個控制線程(或者簡 單地說就是線程)在單進程環(huán)境中執(zhí)行多個任務想虎。一個進程中的所有線 程都可以訪問該進程的組成部件卦尊,如文件描述符和內(nèi)存。
不管在什么情況下磷醋,只要單個資源需要在多個用戶間共享猫牡,就必須 處理一致性問題胡诗。本章的最后將討論目前可用的同步機制邓线,防止多個線 程在共享資源時出現(xiàn)不一致的問題。

11.2 線程概念

典型的UNIX進程可以看成只有一個控制線程:一個進程在某一時 刻只能做一件事情煌恢。有了多個控制線程以后骇陈,在程序設計時就可以把進 程設計成在某一時刻能夠做不止一件事,每個線程處理各自獨立的任 務瑰抵。這種方法有很多好處你雌。

?通過為每種事件類型分配單獨的處理線程,可以簡化處理異步事 件的代碼二汛。每個線程在進行事件處理時可以采用同步編程模式婿崭,同步編 程模式要比異步編程模式簡單得多。

?多個進程必須使用操作系統(tǒng)提供的復雜機制才能實現(xiàn)內(nèi)存和文件 描述符的共享肴颊,我們將在第 15 章和第 17 章中學習這方面的內(nèi)容氓栈。而多 個線程自動地可以訪問相同的存儲地址空間和文件描述符。

?有些問題可以分解從而提高整個程序的吞吐量婿着。在只有一個控制 線程的情況下授瘦,一個單線程進程要完成多個任務,只需要把這些任務串 行化竟宋。但有多個控制線程時提完,相互獨立的任務的處理就可以交叉進行, 此時只需要為每個任務分配一個單獨的線程丘侠。當然只有在兩個任務的處 理過程互不依賴的情況下徒欣,兩個任務才可以交叉執(zhí)行。

?交互的程序同樣可以通過使用多線程來改善響應時間蜗字,多線程可 以把程序中處理用戶輸入輸出的部分與其他部分分開打肝。

有些人把多線程的程序設計與多處理器或多核系統(tǒng)聯(lián)系起來官研。但是 即使程序運行在單處理器上,也能得到多線程編程模型的好處闯睹。處理器 的數(shù)量并不影響程序結(jié)構(gòu)戏羽,所以不管處理器的個數(shù)多少,程序都可以通 過使用線程得以簡化楼吃。而且始花,即使多線程程序在串行化任務時不得不阻 塞,由于某些線程在阻塞的時候還有另外一些線程可以運行孩锡,所以多線程程序在單處理器上運行還是可以改善響應時間和吞吐量酷宵。

每個線程都包含有表示執(zhí)行環(huán)境所必需的信息,其中包括進程中標
識線程的線程ID躬窜、一組寄存器值浇垦、調(diào)度優(yōu)先級策略荣挨、信號屏蔽字男韧、errno變量,以及線程私有數(shù)據(jù)

一個進程的 所有信息對該進程的所有線程都是共享的,包括可執(zhí)行程序的代碼默垄、程 序的全局內(nèi)存和堆內(nèi)存此虑、棧以及文件描述符。

我們將要討論的線程接口來自POSIX.1-2001口锭。線程接口也稱 為“pthread”或“POSIX線程”朦前,原來在POSIX.1-2001中是一個可選功 能,但后來SUSv4把它們放入了基本功能鹃操。POSIX線程的功能測試宏是 _POSIX_THREADS韭寸。應用程序可以把這個宏用于#ifdef測試,從而在編 譯時確定是否支持線程;也可以把_SC_THREADS常數(shù)用于調(diào)用sysconf 函數(shù)荆隘,進而在運行時確定是否支持線程恩伺。遵循SUSv4的系統(tǒng)定義符號 _POSIX_THREADS的值為200809L。

11.3線程標識

就像每個進程有一個進程ID一樣臭胜,每個線程也有一個線程ID莫其。進 程 ID在整個系統(tǒng)中是唯一的,但線程ID不同耸三,線程ID只有在它所屬的 進程上下文中才有意義乱陡。

回憶一下進程ID,它是用pid_t數(shù)據(jù)類型來表示的仪壮,是一個非負整 數(shù)憨颠。線程ID是用pthread_t數(shù)據(jù)類型來表示的,實現(xiàn)的時候可以用一個結(jié) 構(gòu)來代表pthread_t數(shù)據(jù)類型,所以可移植的操作系統(tǒng)實現(xiàn)不能把它作為整數(shù)處理爽彤。因此必須使用一個函數(shù)來對兩個線程ID進行比較养盗。

#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);

返回值:若相等,返回非0數(shù)值;否則适篙,返回0

Linux 3.2.0使用無符號長整型表示pthread_t數(shù)據(jù)類型往核。Solaris 10把pthread_t數(shù)據(jù)類型表示為無符號整型。FreeBSD 8.0和Mac OS X 10.6.8用一個指向pthread結(jié)構(gòu)的指針來表示pthread_t數(shù)據(jù)類型嚷节。

用結(jié)構(gòu)表示pthread_t數(shù)據(jù)類型的后果是不能用一種可移植的方式打 印該數(shù)據(jù)類型的值聂儒。在程序調(diào)試過程中打印線程ID有時是非常有用的, 而在其他情況下通常不需要打印線程ID硫痰。最壞的情況是衩婚,有可能出現(xiàn)不 可移植的調(diào)試代碼,當然這也算不上是很大的局限性效斑。
線程可以通過調(diào)用pthread_self函數(shù)獲得自身的線程ID非春。

#include<pthread.h>
pthread_t pthread_self(void);

返回值:調(diào)用線程的線程ID 當線程需要識別以線程ID作為標識的數(shù)據(jù)結(jié)構(gòu)時,pthread_self函數(shù)可以與pthread_equal函數(shù)一起使用缓屠。例如奇昙,主線程可能把工作任務放在 一個隊列中,用線程ID來控制每個工作線程處理哪些作業(yè)藏研。

如圖11-1所 示敬矩,主線程把新的作業(yè)放到一個工作隊列中概行,由3個工作線程組成的線 程池從隊列中移出作業(yè)蠢挡。主線程不允許每個線程任意處理從隊列頂端取 出的作業(yè),而是由主線程控制作業(yè)的分配凳忙,主線程會在每個待處理作業(yè) 的結(jié)構(gòu)中放置處理該作業(yè)的線程ID业踏,每個工作線程只能移出標有自己線 程ID的作業(yè)。

工作隊列

11.4 線程創(chuàng)建

在傳統(tǒng)UNIX進程模型中涧卵,每個進程只有一個控制線程勤家。從概念上 講,這與基于線程的模型中每個進程只包含一個線程是相同的柳恐。在 POSIX線程(pthread)的情況下伐脖,程序開始運行時,它也是以單進程中 的單個控制線程啟動的乐设。在創(chuàng)建多個控制線程以前讼庇,程序的行為與傳統(tǒng) 的進程并沒有什么區(qū)別。新增的線程可以通過調(diào)用pthread_create函數(shù)創(chuàng) 建近尚。

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *), void *restrict arg);

返回值:若成功蠕啄,返回0;否則,返回錯誤編號 當pthread_create成功返回時,新創(chuàng)建線程的線程ID會被設置成tidp
指向的內(nèi)存單元歼跟。attr參數(shù)用于定制各種不同的線程屬性和媳。我們將在12.3 節(jié)中討論線程屬性,但現(xiàn)在我們把它置為NULL哈街,創(chuàng)建一個具有默認屬 性的線程留瞳。

新創(chuàng)建的線程從start_rtn函數(shù)的地址開始運行,該函數(shù)只有一個無 類型指針參數(shù)arg骚秦。如果需要向start_rtn函數(shù)傳遞的參數(shù)有一個以上撼港,那么需要把這些參數(shù)放到一個結(jié)構(gòu)中,然后把這個結(jié)構(gòu)的地址作為arg參數(shù) 傳入骤竹。

線程創(chuàng)建時并不能保證哪個線程會先運行:是新創(chuàng)建的線程帝牡,還是 調(diào)用線程。新創(chuàng)建的線程可以訪問進程的地址空間蒙揣,并且繼承調(diào)用線程 的浮點環(huán)境和信號屏蔽字靶溜,但是該線程的掛起信號集會被清除。

注意懒震,pthread 函數(shù)在調(diào)用失敗時通常會返回錯誤碼罩息,它們并不像 其他的 POSIX 函數(shù)一樣設置errno。每個線程都提供errno的副本个扰,這只 是為了與使用errno的現(xiàn)有函數(shù)兼容瓷炮。在線程中,從函數(shù)中返回錯誤碼更 為清晰整潔递宅,不需要依賴那些隨著函數(shù)執(zhí)行不斷變化的全局狀態(tài)娘香,這樣 可以把錯誤的范圍限制在引起出錯的函數(shù)中。

#include <pthread.h>
#include <iostream>
#include <memory>
#include <vector>
#include <unistd.h>


/* Function for start. */
void run_thread(int task1, int task2) {
    printf("hello");
    std::cout << "start Function for thread." << std::endl;
    std::cout << "task1: " << task1 << std::endl;
    std::cout << "task1: " << task2 << std::endl;
    // std::cout << "this thread_id: " << pthread_self() << std::endl;
}


void * run_task(void* args) {
    printf("hello this is thread");
    run_thread(1, 2);
    return ((void *)0);
}

pthread_t tid;
int main()
{
    /* Pthread Id. */
    pthread_t tid_1 = reinterpret_cast<pthread_t>(1);
    pthread_t tid_2 = reinterpret_cast<pthread_t>(1);
    
    if (pthread_equal(tid_1, tid_2)) {
        std::cout << "equal pthread" << std::endl;
    }

    /* Get self Id. */
    pthread_t my_tid = pthread_self();
    std::cout << "self _Id: " << my_tid << std::endl;

    void (*thread_task)(int , int ) = run_thread;

    int arg_list[10] = {1, 2};

    int *arg = arg_list;
    printf("Test pthread\n");

    /* Start Create Thread. */
    int ret = pthread_create(&tid, NULL, run_task, NULL);
    sleep(1);

    /* thread queue. */
    return 0;
}
image.png

這個實例有兩個特別之處办龄,需要處理主線程和新線程之間的競爭烘绽。 (我們將在這章后面的內(nèi)容中學習如何更好地處理這種競爭。)第一個 特別之處在于俐填,主線程需要休眠安接,如果主線程不休眠,它就可能會退 出英融,這樣新線程還沒有機會運行盏檐,整個進程可能就已經(jīng)終止了。這種行 為特征依賴于操作系統(tǒng)中的線程實現(xiàn)和調(diào)度算法驶悟。

第二個特別之處在于新線程是通過調(diào)用pthread_self函數(shù)獲取自己的 線程ID的胡野,而不是從共享內(nèi)存中讀出的,或者從線程的啟動例程中以參 數(shù)的形式接收到的撩银「椋回憶 pthread_create函數(shù),它會通過第一個參數(shù)
(tidp)返回新建線程的線程ID。在這個例子中够庙,主線程把新線程ID存 放在 ntid 中恭应,但是新建的線程并不能安全地使用它,如果新線程在主線 程調(diào)用pthread_create返回之前就運行了耘眨,那么新線程看到的是未經(jīng)初始 化的ntid的內(nèi)容昼榛,這個內(nèi)容并不是正確的線程ID。

盡管Linux線程ID是用無符號長整型來表示的剔难,但是它們看起來像
指針胆屿。

Linux 2.4和Linux 2.6在線程實現(xiàn)上是不同的。Linux 2.4中偶宫, LinuxThreads是用單獨的進程實現(xiàn)每個線程的非迹,這使得它很難與POSIX 線程的行為匹配。Linux 2.6中纯趋,對Linux內(nèi)核和線程庫進行了很大的修 改憎兽,采用了一個稱為Native POSIX線程庫(Native POSIX Thread Library,NPTL)的新線程實現(xiàn)吵冒。它支持單個進程中有多個線程的模 型纯命,也更容易支持POSIX線程的語義。

11.5 線程終止

如果進程中的任意線程調(diào)用了 exit痹栖、_Exit 或者_exit亿汞,那么整個進程 就會終止。與此相類似揪阿,如果默認的動作是終止進程疗我,那么,發(fā)送到線 程的信號就會終止整個進程图甜,單個線程可以通過3種方式退出碍粥,因此可以在不終止整個進程的情 況下,停止它的控制流黑毅。

(1)線程可以簡單地從啟動例程中返回,返回值是線程的退出 碼钦讳。
(2)線程可以被同一進程中的其他線程取消矿瘦。
 (3)線程調(diào)用pthread_exit。

#include <pthread.h>
void pthread_exit(void *rval_ptr);

rval_ptr 參數(shù)是一個無類型指針愿卒,與傳給啟動例程的單個參數(shù)類
似缚去。進程中的其他線程也可以通過調(diào)用pthread_join函數(shù)訪問到這個指 針。

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);

返回值:若成功琼开,返回0;否則易结,返回錯誤編號

調(diào)用線程將一直阻塞,直到指定的線程調(diào)用pthread_exit、從啟動例
程中返回或者被取消搞动。如果線程簡單地從它的啟動例程返回躏精,rval_ptr就 包含返回碼。如果線程被取消鹦肿,由rval_ptr指定的內(nèi)存單元就設置為 PTHREAD_CANCELED矗烛。

可以通過調(diào)用pthread_join自動把線程置于分離狀態(tài)(馬上就會討論 到),這樣資源就可以恢復箩溃。如果線程已經(jīng)處于分離狀態(tài)瞭吃,pthread_join 調(diào)用就會失敗,返回EINVAL涣旨,盡管這種行為是與具體實現(xiàn)相關(guān)的歪架。
如果對線程的返回值并不感興趣,那么可以把rval_ptr設置為 NULL霹陡。在這種情況下牡拇,調(diào)用pthread_join函數(shù)可以等待指定的線程終 止,但并不獲取線程的終止狀態(tài)穆律。

#include <iostream>
#include <vector>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>

void *th_func(void *) {
    std::cout << "After Running Thread task1" << std::endl;
    return (void *)1;
}

void *th_task_2(void *) {
    std::cout << "After Running Thread task2" << std::endl;

    pthread_exit(NULL);
}

int main() {

    void *rval_ptr;
    pthread_t tid_test;
    pthread_t tid_2;
    int err;

    std::cout << "Start Main Thread" << std::endl;

    /* Thread 1. */
    err = pthread_create(&tid_test, NULL, th_func, NULL);
    if(err != 0) {
        std::cout << err << "can't create thread 1" << std::endl;
    }
    printf("Thread1 exit code %ld\n", *static_cast<long *>(rval_ptr));

    /* Thread 2. */
    err= pthread_create(&tid_2, NULL, th_task_2, NULL);
    if(err != 0) {
        std::cout << err << "can't create thread 2" << std::endl;
    }

    err = pthread_join(tid_test, &rval_ptr);

    /* Join Thread2. */
    err = pthread_join(tid_2, &rval_ptr);
    if(err != 0) {
        std::cout << err << "can't join thread2" << std::endl;
    }

    printf("Thread2 exit code %ld\n", *static_cast<long*>(rval_ptr));
    exit(0);
}

可以看到惠呼,當一個線程通過調(diào)用pthread_exit退出或者簡單地從啟動
例程中返回時,進程中的其他線程可以通過調(diào)用pthread_join函數(shù)獲得該 線程的退出狀態(tài)峦耘。
pthread_create和pthread_exit函數(shù)的無類型指針參數(shù)可以傳遞的值不 止一個剔蹋,這個指針可以傳遞包含復雜信息的結(jié)構(gòu)的地址,但是注意辅髓,這 個結(jié)構(gòu)所使用的內(nèi)存在調(diào)用者完成調(diào)用以后必須仍然是有效的泣崩。例如, 在調(diào)用線程的棧上分配了該結(jié)構(gòu)洛口,那么其他的線程在使用這個結(jié)構(gòu)時內(nèi) 存內(nèi)容可能已經(jīng)改變了矫付。又如,線程在自己的棧上分配了一個結(jié)構(gòu)第焰,然 后把指向這個結(jié)構(gòu)的指針傳給pthread_exit买优,那么調(diào)用pthread_join的線程 試圖使用該結(jié)構(gòu)時,這個棧有可能已經(jīng)被撤銷挺举,這塊內(nèi)存也已另作他 用杀赢。

雖然線程退出后,內(nèi)存依然是完整的湘纵,但我們不能期望情況總是這
樣的脂崔。從其他平臺上的結(jié)果中可以看出,情況并不都是這樣的梧喷。 線程可以通過調(diào)用pthread_cancel函數(shù)來請求取消同一進程中的其他線程砌左。

#include <pthread.h>
int pthread_cancel(pthread_t tid);

返回值:若成功脖咐,返回0;否則,返回錯誤編號

返回值:若成功汇歹,返回0;否則屁擅,返回錯誤編號 在默認情況下,pthread_cancel 函數(shù)會使得由tid標識的線程的行為表 現(xiàn)為如同調(diào)用了參數(shù)為PTHREAD_ CANCELED 的pthread_exit 函數(shù)秤朗,但
是煤蹭,線程可以選擇忽略取消或者控制如何被取消。我們將在12.7節(jié)中詳 細討論取视。注意pthread_cancel并不等待線程終止硝皂,它僅僅提出請求。
線程可以安排它退出時需要調(diào)用的函數(shù)作谭,這與進程在退出時可以用 atexit函數(shù)(見7.3節(jié))安排退出是類似的稽物。這樣的函數(shù)稱為線程清理處 理程序(thread cleanup handler)。一個線程可以建立多個清理處理程 序折欠。處理程序記錄在棧中贝或,也就是說,它們的執(zhí)行順序與它們注冊時相 反锐秦。

#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg); void pthread_cleanup_pop(int execute);
void *hello(void* temp) {
    do something
    pthread_cleanup_push(func, arg)
        pthread_cleanup_pop(0);
    return (void *)0;
}

現(xiàn)在咪奖,讓我們了解一下線程函數(shù)和進程函數(shù)之間的相似之處。圖 11-6總結(jié)了這些相似的函數(shù)酱床。


image.png

在默認情況下羊赵,線程的終止狀態(tài)會保存直到對該線程調(diào)用 pthread_join。如果線程已經(jīng)被分離扇谣,線程的底層存儲資源可以在線程終 止時立即被收回昧捷。在線程被分離后,我們不能用pthread_join函數(shù)等待它 的終止狀態(tài)罐寨,因為對分離狀態(tài)的線程調(diào)用pthread_join會產(chǎn)生未定義行 為靡挥。可以調(diào)用pthread_detach分離線程鸯绿。

#include <pthread.h>
int pthread_detach(pthread_t tid);

返回值:若成功跋破,返回0;否則,返回錯誤編號 在下一章里楞慈,我們將學習通過修改傳給pthread_create函數(shù)的線程屬
性幔烛,創(chuàng)建一個已處于分離狀態(tài)的線程。

11.7 線程同步

當多個控制線程共享相同的內(nèi)存時囊蓝,需要確保每個線程看到一致的 數(shù)據(jù)視圖。如果每個線程使用的變量都是其他線程不會讀取和修改的令蛉, 那么就不存在一致性問題聚霜。同樣狡恬,如果變量是只讀的,多個線程同時讀 取該變量也不會有一致性問題蝎宇。但是弟劲,當一個線程可以修改的變量,其 他線程也可以讀取或者修改的時候姥芥,我們就需要對這些線程進行同步兔乞, 確保它們在訪問變量的存儲內(nèi)容時不會訪問到無效的值。
當一個線程修改變量時凉唐,其他線程在讀取這個變量時可能會看到一 個不一致的值庸追。在變量修改時間多于一個存儲器訪問周期的處理器結(jié)構(gòu) 中,當存儲器讀與存儲器寫這兩個周期交叉時台囱,這種不一致就會出現(xiàn)淡溯。 當然,這種行為是與處理器體系結(jié)構(gòu)相關(guān)的簿训,但是可移植的程序并不能 對使用何種處理器體系結(jié)構(gòu)做出任何假設咱娶。

圖 11-7 描述了兩個線程讀寫相同變量的假設例子。在這個例子 中强品,線程 A讀取變量然后給這個變量賦予一個新的數(shù)值膘侮,但寫操作需要 兩個存儲器周期。當線程B在這兩個存儲器寫周期中間讀取這個變量 時的榛,它就會得到不一致的值琼了。


image.png
image.png

為了解決這個問題,線程不得不使用鎖困曙,同一時間只允許一個線程 訪問該變量表伦。圖11-8描述了這種同步。如果線程B希望讀取變量慷丽,它首 先要獲取鎖蹦哼。同樣,當線程A更新變量時要糊,也需要獲取同樣的這把鎖纲熏。 這樣,線程B在線程A釋放鎖以前就不能讀取變量锄俄。

兩個或多個線程試圖在同一時間修改同一變量時局劲,也需要進行同 步∧淘考慮變量增量操作的情況(圖11-9)鱼填,增量操作通常分解為以下3 步。
(1)從內(nèi)存單元讀入寄存器毅戈。
(2)在寄存器中對變量做增量操作苹丸。
(3)把新的值寫回內(nèi)存單元愤惰。

如果兩個線程試圖幾乎在同一時間對同一個變量做增量操作而不進
行同步的話,結(jié)果就可能出現(xiàn)不一致赘理,變量可能比原來增加了1宦言,也有 可能比原來增加了2,具體增加了1還是2要取決于第二個線程開始操作 時獲取的數(shù)值商模。如果第二個線程執(zhí)行第1步要比第一個線程執(zhí)行第3步要 早奠旺,第二個線程讀到的值與第一個線程一樣,為變量加1施流,然后寫回 去响疚,事實上沒有實際的效果,總的來說變量只增加了1嫂沉。

如果修改操作是原子操作稽寒,那么就不存在競爭。在前面的例子中趟章, 如果增加1只需要一個存儲器周期杏糙,那么就沒有競爭存在。如果數(shù)據(jù)總 是以順序一致出現(xiàn)的蚓土,就不需要額外的同步宏侍。當多個線程觀察不到數(shù)據(jù) 的不一致時,那么操作就是順序一致的蜀漆。在現(xiàn)代計算機系統(tǒng)中谅河,存儲訪 問需要多個總線周期,多處理器的總線周期通常在多個處理器上是交叉 的确丢,所以我們并不能保證數(shù)據(jù)是順序一致的绷耍。

image.png

順序一致環(huán)境中,可以把數(shù)據(jù)修改操作解釋為運行線程的順序操 作步驟鲜侥∽琶保可以把這樣的操作描述為“線程A對變量增加了1帆吻,然后線程B 對變量增加了1粱哼,所以變量的值就比原來的大2”些阅,或者描述為“線程B 對變量增加了1,然后線程A對變量增加了1舀寓,所以變量的值就比原來的 大2”胆数。這兩個線程的任何操作順序都不可能讓變量出現(xiàn)除了上述值以 外的其他值。
除了計算機體系結(jié)構(gòu)以外互墓,程序使用變量的方式也會引起競爭必尼,也 會導致不一致的情況發(fā)生。例如篡撵,我們可能對某個變量加 1胰伍,然后基于
這個值做出某種決定齿诞。因為這個增量操作步驟和這個決定步驟的組合并 非原子操作酸休,所以就給不一致情況的出現(xiàn)提供了可能骂租。

11.6互斥量

互斥變量是用pthread_mutex_t數(shù)據(jù)類型表示的。在使用互斥變量以 前斑司,必須首先對它進行初始化渗饮,可以把它設置為常量 PTHREAD_MUTEX_INITIALIZER(只適用于靜態(tài)分配的互斥量),也 可以通過調(diào)用pthread_mutex_init函數(shù)進行初始化宿刮。如果動態(tài)分配互斥量 (例如互站,通過調(diào)用malloc函數(shù)),在釋放內(nèi)存前需要調(diào)用,pthread_mutex_destroy僵缺。

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
 
int pthread_mutex_destroy(pthread_mutex_t *mutex);

兩個函數(shù)的返回值:若成功胡桃,返回0;否則,返回錯誤編號 要用默認的屬性初始化互斥量磕潮,只需把attr設為NULL.

對互斥量進行加鎖翠胰,需要調(diào)用 pthread_mutex_lock。如果互斥量已
經(jīng)上鎖自脯,調(diào)用線程將阻塞直到互斥量被解鎖之景。對互斥量解鎖,需要調(diào)用 pthread_mutex_unlock膏潮。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);


所有函數(shù)的返回值:若成功锻狗,返回0;否則,返回錯誤編號 如果線程不希望被阻塞焕参,它可以使用pthread_mutex_trylock嘗試對互 斥量進行加鎖轻纪。如果調(diào)用 pthread_mutex_trylock 時互斥量處于未鎖住狀態(tài),那pthread_mutex_trylock將鎖住互斥量叠纷,不會出現(xiàn)阻塞直接返回 0刻帚,否則pthread_mutex_trylock 就會失敗,不能鎖住互斥量讲岁,返回 EBUSY我擂。

保護某個數(shù)據(jù)結(jié)構(gòu)的互斥量。當一個以上的線程 需要訪問動態(tài)分配的對象時缓艳,我們可以在對象中嵌入引用計數(shù)校摩,確保在 所有使用該對象的線程完成數(shù)據(jù)訪問之前,該對象內(nèi)存空間不會被釋 放阶淘。

在對引用計數(shù)加 1衙吩、減 1、檢查引用計數(shù)是否到達 0 這些操作之前 需要鎖住互斥量溪窒。在foo_alloc 函數(shù)中將引用計數(shù)初始化為 1 時沒必要加 鎖坤塞,因為在這個操作之前分配線程是唯一引用該對象的線程冯勉。但是在這 之后如果要將該對象放到一個列表中,那么它就有可能被別的線程發(fā) 現(xiàn)摹芙,這時候需要首先對它加鎖灼狰。

struct foo{
    int f_count;
    pthread_mutex_t f_mutex;
    int f_id;
};

foo * foo_alloc(int id) {
    foo *fp;
    
    if((fp = (foo*)malloc(sizeof(sizeof(foo)))) != NULL) {
        fp->f_count = 1;
        fp->f_id = id;
        if(pthread_mutex_init(&fp->f_mutex, NULL) != 0) {
            free(fp);
            return NULL;
        }
    }

    return fp;
}

void foo_hold(foo *fp) {
    pthread_mutex_lock(&fp->f_mutex);
    fp->f_count++;
    pthread_mutex_unlock(&fp->f_mutex);
}

void foo_release(foo *fp) {
    pthread_mutex_lock(&fp->f_mutex);

    if(--fp->f_count == 0) {
        pthread_mutex_unlock(&fp->f_mutex);
        pthread_mutex_destroy(&fp->f_mutex);
        free(fp);
    } else {
        pthread_mutex_unlock(&fp->f_mutex);
    }
}

11.6 避免死鎖

如果線程試圖對同一個互斥量加鎖兩次,那么它自身就會陷入死鎖 狀態(tài)浮禾,但是使用互斥量時交胚,還有其他不太明顯的方式也能產(chǎn)生死鎖。例 如盈电,程序中使用一個以上的互斥量時蝴簇,如果允許一個線程一直占有第一 個互斥量,并且在試圖鎖住第二個互斥量時處于阻塞狀態(tài)匆帚,但是擁有第 二個互斥量的線程也在試圖鎖住第一個互斥量熬词。因為兩個線程都在相互 請求另一個線程擁有的資源,所以這兩個線程都無法向前運行吸重,于是就 產(chǎn)生死鎖互拾。

可以通過仔細控制互斥量加鎖的順序來避免死鎖的發(fā)生。例如晤锹,假 設需要對兩個互斥量A和B同時加鎖摩幔。如果所有線程總是在對互斥量B加 鎖之前鎖住互斥量A,那么使用這兩個互斥量就不會產(chǎn)生死鎖(當然在 其他的資源上仍可能出現(xiàn)死鎖)鞭铆。類似地或衡,如果所有的線程總是在鎖住 互斥量A之前鎖住互斥量B,那么也不會發(fā)生死鎖车遂》舛希可能出現(xiàn)的死鎖只 會發(fā)生在一個線程試圖鎖住另一個線程以相反的順序鎖住的互斥量。
有時候舶担,應用程序的結(jié)構(gòu)使得對互斥量進行排序是很困難的坡疼。如果 涉及了太多的鎖和數(shù)據(jù)結(jié)構(gòu),可用的函數(shù)并不能把它轉(zhuǎn)換成簡單的層 次衣陶,那么就需要采用另外的方法柄瑰。在這種情況下,可以先釋放占有的 鎖剪况,然后過一段時間再試教沾。這種情況可以使用pthread_mutex_trylock接口 避免死鎖。如果已經(jīng)占有某些鎖而且pthread_mutex_trylock接口返回成 功译断,那么就可以前進授翻。但是,如果不能獲取鎖,可以先釋放已經(jīng)占有的 鎖堪唐,做好清理工作巡语,然后過一段時間再重新試。

11.6.3 函數(shù)pthread_mutex_timedlock

當線程試圖獲取一個已加鎖的互斥量時淮菠,pthread_mutex_timedlock 互斥量原語允許綁定線程阻塞時間男公。pthread_mutex_timedlock函數(shù)與 pthread_mutex_lock是基本等價的,但是在達到超時時間值時兜材, pthread_mutex_timedlock 不會對互斥量進行加鎖理澎,而是返回錯誤碼ETIMEDOUT。

#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t
mutex,
              const struct timespec *restrict tsptr);

返回值:若成功曙寡,返回0;否則,返回錯誤編號 超時指定愿意等待的絕對時間(與相對時間對比而言寇荧,指定在時間X之前可以阻塞等待举庶,而不是說愿意阻塞Y秒)。這個超時時間是用 timespec結(jié)構(gòu)來表示的揩抡,它用秒和納秒來描述時間户侥。

11.6.4 讀寫鎖

讀寫鎖(reader-writer lock)與互斥量類似,不過讀寫鎖允許更高的 并行性峦嗤∪锾疲互斥量要么是鎖住狀態(tài),要么就是不加鎖狀態(tài)烁设,而且一次只有 一個線程可以對其加鎖替梨。讀寫鎖可以有3種狀態(tài):讀模式下加鎖狀態(tài), 寫模式下加鎖狀態(tài)装黑,不加鎖狀態(tài)副瀑。一次只有一個線程可以占有寫模式的 讀寫鎖,但是多個線程可以同時占有讀模式的讀寫鎖恋谭。

當讀寫鎖是寫加鎖狀態(tài)時糠睡,在這個鎖被解鎖之前,所有試圖對這個 鎖加鎖的線程都會被阻塞疚颊。當讀寫鎖在讀加鎖狀態(tài)時狈孔,所有試圖以讀模 式對它進行加鎖的線程都可以得到訪問權(quán),但是任何希望以寫模式對此 鎖進行加鎖的線程都會阻塞材义,直到所有的線程釋放它們的讀鎖為止均抽。雖 然各操作系統(tǒng)對讀寫鎖的實現(xiàn)各不相同,但當讀寫鎖處于讀模式鎖住的 狀態(tài)母截,而這時有一個線程試圖以寫模式獲取鎖時到忽,讀寫鎖通常會阻塞隨 后的讀模式鎖請求。這樣可以避免讀模式鎖長期占用,而等待的寫模式 鎖請求一直得不到滿足喘漏。

讀寫鎖非常適合于對數(shù)據(jù)結(jié)構(gòu)讀的次數(shù)遠大于寫的情況护蝶。當讀寫鎖 在寫模式下時,它所保護的數(shù)據(jù)結(jié)構(gòu)就可以被安全地修改翩迈,因為一次只 有一個線程可以在寫模式下?lián)碛羞@個鎖持灰。當讀寫鎖在讀模式下時,只要 線程先獲取了讀模式下的讀寫鎖负饲,該鎖所保護的數(shù)據(jù)結(jié)構(gòu)就可以被多個

獲得讀模式鎖的線程讀取堤魁。
讀寫鎖也叫做共享互斥鎖(shared-exclusive lock)。當讀寫鎖是讀
模式鎖住時返十,就可以說成是以共享模式鎖住的妥泉。當它是寫模式鎖住的時 候,就可以說成是以互斥模式鎖住的洞坑。
與互斥量相比盲链,讀寫鎖在使用之前必須初始化,在釋放它們底層的 內(nèi)存之前必須銷毀迟杂。

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

兩個函數(shù)的返回值:若成功刽沾,返回0;否則,返回錯誤編號 讀寫鎖通過調(diào)用 pthread_rwlock_init 進行初始化排拷。如果希望讀寫鎖 有默認的屬性侧漓,可以傳一個null指針給attr,我們將在12.4.2節(jié)中討論讀寫
鎖的屬性监氢。
Single UNIX Specification在XSI擴展中定義了
PTHREAD_RWLOCK_INITIALIZER常量布蔗。如果默認屬性就足夠的話, 可以用它對靜態(tài)分配的讀寫鎖進行初始化忙菠。
在釋放讀寫鎖占用的內(nèi)存之前何鸡,需要調(diào)用 pthread_rwlock_destroy 做 清理工作。如果pthread_rwlock_init為讀寫鎖分配了資源牛欢, pthread_rwlock_destroy將釋放這些資源骡男。如果在調(diào)用 pthread_rwlock_destroy 之前就釋放了讀寫鎖占用的內(nèi)存空間,那么分配 給這個鎖的資源就會丟失傍睹。

要在讀模式下鎖定讀寫鎖隔盛,需要調(diào)用pthread_rwlock_rdlock。要在寫 模式下鎖定讀寫鎖拾稳,需要調(diào)用pthread_rwlock_wrlock吮炕。不管以何種方式 鎖住讀寫鎖,都可以調(diào)用pthread_rwlock_unlock進行解鎖访得。

#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

有函數(shù)的返回值:若成功龙亲,返回0;否則陕凹,返回錯誤編號 各種實現(xiàn)可能會對共享模式下可獲取的讀寫鎖的次數(shù)進行限制,所
以需要檢查 pthread_rwlock_rdlock的返回值鳄炉。即使pthread_rwlock_wrlock 和pthread_rwlock_unlock有錯誤返回杜耙,而且從技術(shù)上來講,在調(diào)用函數(shù) 時應該總是檢查錯誤返回拂盯,但是如果鎖設計合理的話佑女,就不需要檢查它 們。錯誤返回值的定義只是針對不正確使用讀寫鎖的情況(如未經(jīng)初始 化的鎖)谈竿,或者試圖獲取已擁有的鎖從而可能產(chǎn)生死鎖的情況团驱。但是需 要注意,有些特定的實現(xiàn)可能會定義另外的錯誤返回空凸。

Single UNIX Specification還定義了讀寫鎖原語的條件版本嚎花。
#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); 
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

兩個函數(shù)的返回值:若成功,返回0;否則劫恒,返回錯誤編號 可以獲取鎖時贩幻,這兩個函數(shù)返回0。否則两嘴,它們返回錯誤EBUSY。
這兩個函數(shù)可以用于我們前面討論的遵守某種鎖層次但還不能完全避免 死鎖的情況族壳。

凡是需要向隊列中增加作業(yè)或者從隊列中刪除作業(yè) 的時候憔辫,都采用了寫模式來鎖住隊列的讀寫鎖。不管何時搜索隊列仿荆,都 需要獲取讀模式下的鎖贰您,允許所有的工作線程并發(fā)地搜索隊列。在這種 情況下拢操,只有在線程搜索作業(yè)的頻率遠遠高于增加或刪除作業(yè)時锦亦,使用 讀寫鎖才可能改善性能。
工作線程只能從隊列中讀取與它們的線程 ID 匹配的作業(yè)令境。由于作 業(yè)結(jié)構(gòu)同一時間只能由一個線程使用杠园,所以不需要額外的加鎖。

11.6.5 帶有超時的讀寫鎖

與互斥量一樣舔庶,Single UNIX Specification提供了帶有超時的讀寫鎖 加鎖函數(shù)抛蚁,使應用程序在獲取讀寫鎖時避免陷入永久阻塞狀態(tài)。這兩個 函數(shù)是 pthread_rwlock_timedrdlock 和 pthread_rwlock_timedwrlock惕橙。

#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict
 
rwlock,
const struct timespec *restrict tsptr);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict
rwlock,
                const struct timespec *restrict tsptr);

兩個函數(shù)的返回值:若成功瞧甩,返回0;否則,返回錯誤編號 這兩個函數(shù)的行為與它們“不計時的”版本類似弥鹦。tsptr參數(shù)指向
timespec結(jié)構(gòu)肚逸,指定線程應該停止阻塞的時間。如果它們不能獲取鎖, 那么超時到期時朦促,這兩個函數(shù)將返回 ETIMEDOUT錯誤膝晾。與 pthread_mutex_timedlock函數(shù)類似,超時指定的是絕對時間思灰,而不是相 對時間玷犹。

11.6.7 條件變量

條件變量是線程可用的另一種同步機制。條件變量給多個線程提供 了一個會合的場所洒疚。條件變量與互斥量一起使用時歹颓,允許線程以無競爭 的方式等待特定的條件發(fā)生。

條件本身是由互斥量保護的油湖。線程在改變條件狀態(tài)之前必須首先鎖 住互斥量巍扛。其他線程在獲得互斥量之前不會察覺到這種改變,因為互斥 量必須在鎖定以后才能計算條件乏德。
在使用條件變量之前撤奸,必須先對它進行初始化。由pthread_cond_t數(shù) 據(jù)類型表示的條件變量可以用兩種方式進行初始化喊括,可以把常量 PTHREAD_COND_INITIALIZER賦給靜態(tài)分配的條件變量胧瓜,但是如果 條件變量是動態(tài)分配的,則需要使用pthread_cond_init函數(shù)對它進行初始 化郑什。
在釋放條件變量底層的內(nèi)存空間之前府喳,可以使用 pthread_cond_destroy函數(shù)對條件變量進行反初始化(deinitialize)。

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); 
int pthread_cond_destroy(pthread_cond_t *cond);

兩個函數(shù)的返回值:若成功蘑拯,返回0;否則钝满,返回錯誤編號 除非需要創(chuàng)建一個具有非默認屬性的條件變量,否則
pthread_cond_init函數(shù)的attr參數(shù)可以設置為NULL申窘。我們將在12.4.3節(jié)中 討論條件變量屬性弯蚜。

我們使用pthread_cond_wait等待條件變量變?yōu)檎妗H绻诮o定的時 間內(nèi)條件不能滿足剃法,那么會生成一個返回錯誤碼的變量碎捺。

#include <pthread.h>
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 tsptr);

兩個函數(shù)的返回值:若成功,返回0;否則玄窝,返回錯誤編號 傳遞給pthread_cond_wait的互斥量對條件進行保護牵寺。調(diào)用者把鎖住
的互斥量傳給函數(shù),函數(shù)然后自動把調(diào)用線程放到等待條件的線程列表 上恩脂,對互斥量解鎖帽氓。這就關(guān)閉了條件檢查和線程進入休眠狀態(tài)等待條件 改變這兩個操作之間的時間通道,這樣線程就不會錯過條件的任何變 化俩块。pthread_cond_wait返回時黎休,互斥量再次被鎖住浓领。

pthread_cond_timedwait函數(shù)的功能與pthread_cond_wait函數(shù)相似,只 是多了一個超時(tsptr)势腮。超時值指定了我們愿意等待多長時間联贩,它是 通過timespec結(jié)構(gòu)指定的。

如圖11-13所示捎拯,需要指定愿意等待多長時間泪幌,這個時間值是一個絕 對數(shù)而不是相對數(shù)。例如署照,假設愿意等待3分鐘祸泪。那么,并不是把3分鐘
轉(zhuǎn)換成timespec結(jié)構(gòu)建芙,而是需要把當前時間加上3分鐘再轉(zhuǎn)換成timespec 結(jié)構(gòu)没隘。
可以使用clock_gettime函數(shù)(見6.10節(jié))獲取timespec結(jié)構(gòu)表示的當 前時間。但是目前并不是所有的平臺都支持這個函數(shù)禁荸,因此右蒲,也可以用 另一個函數(shù) gettimeofday 獲取timeval結(jié)構(gòu)表示的當前時間,然后把這個 時間轉(zhuǎn)換成timespec結(jié)構(gòu)赶熟。

如果超時到期時條件還是沒有出現(xiàn)瑰妄,pthread_cond_timewait 將重新 獲取互斥量,然后返回錯誤ETIMEDOUT映砖。從pthread_cond_wait或者 pthread_cond_timedwait調(diào)用成功返回時翰撑,線程需要重新計算條件,因為 另一個線程可能已經(jīng)在運行并改變了條件啊央。
有兩個函數(shù)可以用于通知線程條件已經(jīng)滿足。pthread_cond_signal函 數(shù)至少能喚醒一個等待該條件的線程涨醋,而pthread_cond_broadcast函數(shù)則 能喚醒等待該條件的所有線程瓜饥。
POSIX 規(guī)范為了簡化 pthread_cond_signal 的實現(xiàn),允許它在實 現(xiàn)的時候喚醒一個以上的線程浴骂。

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

兩個函數(shù)的返回值:若成功乓土,返回0;否則,返回錯誤編號 在調(diào)用pthread_cond_signal或者pthread_cond_broadcast時溯警,我們說這
是在給線程或者條件發(fā)信號趣苏。必須注意,一定要在改變條件狀態(tài)以后再 給線程發(fā)信號梯轻。

#include <iostream>
#include <pthread.h>
#include <vector>
#include <string>

struct msg {
    msg *next;
    std::string str;
};

msg *work_queue;

pthread_cond_t q_ready = PTHREAD_COND_INITIALIZER;

pthread_mutex_t q_lock = PTHREAD_MUTEX_INITIALIZER;

void handle_msg(void) {
    msg *tmp;

    while(true) {
        pthread_mutex_lock(&q_lock);
        while(work_queue == NULL) {
            pthread_cond_wait(&q_ready, &q_lock);
        }

        tmp = work_queue;
        work_queue = tmp->next;
        pthread_mutex_unlock(&q_lock);
    }
}

void enqueue_msg(msg * tmp) {
    pthread_mutex_lock(&q_lock);
    tmp->next = work_queue;
    work_queue = tmp;
    pthread_mutex_unlock(&q_lock);
    pthread_cond_signal(&q_ready);
}

條件是工作隊列的狀態(tài)食磕。我們用互斥量保護條件,在 while 循環(huán)中 判斷條件喳挑。把消息放到工作隊列時彬伦,需要占有互斥量滔悉,但在給等待線程 發(fā)信號時,不需要占有互斥量单绑。只要線程在調(diào)用pthread_cond_signal之前 把消息從隊列中拖出了回官,就可以在釋放互斥量以后完成這部分工作。因 為我們是在 while 循環(huán)中檢查條件搂橙,所以不存在這樣的問題:線程醒 來歉提,發(fā)現(xiàn)隊列仍為空,然后返回繼續(xù)等待区转。如果代碼不能容忍這種競 爭苔巨,就需要在給線程發(fā)信號的時候占有互斥量。

11.6.7自旋鎖

自旋鎖與互斥量類似蜗帜,但它不是通過休眠使進程阻塞恋拷,而是在獲取 鎖之前一直處于忙等(自旋)阻塞狀態(tài)。自旋鎖可用于以下情況:鎖被 持有的時間短厅缺,而且線程并不希望在重新調(diào)度上花費太多的成本蔬顾。

自旋鎖通常作為底層原語用于實現(xiàn)其他類型的鎖。根據(jù)它們所基于 的系統(tǒng)體系結(jié)構(gòu)湘捎,可以通過使用測試并設置指令有效地實現(xiàn)诀豁。當然這里 說的有效也還是會導致CPU資源的浪費:當線程自旋等待鎖變?yōu)榭捎?時,CPU不能做其他的事情窥妇。這也是自旋鎖只能夠被持有一小段時間的

原因舷胜。 當自旋鎖用在非搶占式內(nèi)核中時是非常有用的:除了提供互斥機制
以外,它們會阻塞中斷活翩,這樣中斷處理程序就不會讓系統(tǒng)陷入死鎖狀 態(tài)烹骨,因為它需要獲取已被加鎖的自旋鎖(把中斷想成是另一種搶占)。 在這種類型的內(nèi)核中材泄,中斷處理程序不能休眠沮焕,因此它們能用的同步原 語只能是自旋鎖。

但是拉宗,在用戶層峦树,自旋鎖并不是非常有用,除非運行在不允許搶占 的實時調(diào)度類中旦事。運行在分時調(diào)度類中的用戶層線程在兩種情況下可以 被取消調(diào)度:當它們的時間片到期時魁巩,或者具有更高調(diào)度優(yōu)先級的線程 就緒變成可運行時。在這些情況下姐浮,如果線程擁有自旋鎖谷遂,它就會進入 休眠狀態(tài),阻塞在鎖上的其他線程自旋的時間可能會比預期的時間更 長单料。
很多互斥量的實現(xiàn)非常高效埋凯,以至于應用程序采用互斥鎖的性能與 曾經(jīng)采用過自旋鎖的性能基本是相同的点楼。事實上,有些互斥量的實現(xiàn)在 試圖獲取互斥量的時候會自旋一小段時間白对,只有在自旋計數(shù)到達某一閾 值的時候才會休眠掠廓。這些因素,加上現(xiàn)代處理器的進步甩恼,使得上下文切 換越來越快蟀瞧,也使得自旋鎖只在某些特定的情況下有用。

自旋鎖的接口與互斥量的接口類似条摸,這使得它可以比較容易地從一 個替換為另一個悦污。可以用pthread_spin_init 函數(shù)對自旋鎖進行初始化钉蒲。用 pthread_spin_destroy 函數(shù)進行自旋鎖的反初始化切端。

#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);

兩個函數(shù)的返回值:若成功,返回0;否則顷啼,返回錯誤編號 只有一個屬性是自旋鎖特有的踏枣,這個屬性只在支持線程進程共享同
步(Thread Process-Shared Synchronization)選項(這個選項目前在SingleUNIX Specification中是強制的,見圖2-5)的平臺上才用得到钙蒙。pshared 參數(shù)表示進程共享屬性茵瀑,表明自旋鎖是如何獲取的。如果它設為 PTHREAD_PROCESS_SHARED躬厌,則自旋鎖能被可以訪問鎖底層內(nèi)存的 線程所獲取马昨,即便那些線程屬于不同的進程,情況也是如此扛施。否則 pshared參數(shù)設為 PTHREAD_PROCESS_PRIVATE鸿捧,自旋鎖就只能被初 始化該鎖的進程內(nèi)部的線程所訪問。
可以用pthread_spin_lock或pthread_spin_trylock對自旋鎖進行加鎖疙渣, 前者在獲取鎖之前一直自旋笛谦,后者如果不能獲取鎖,就立即返回EBUSY 錯誤昌阿。注意,pthread_spin_trylock不能自旋恳邀。不管以何種方式加鎖懦冰,自 旋鎖都可以調(diào)用pthread_spin_unlock函數(shù)解鎖。

#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock); int pthread_spin_trylock(pthread_spinlock_t *lock); int pthread_spin_unlock(pthread_spinlock_t *lock);


11.6.8 屏障

屏障(barrier)是用戶協(xié)調(diào)多個線程并行工作的同步機制谣沸。屏障允 許每個線程等待刷钢,直到所有的合作線程都到達某一點,然后從該點繼續(xù) 執(zhí)行乳附。我們已經(jīng)看到一種屏障内地,pthread_join函數(shù)就是一種屏障伴澄,允許一 個線程等待,直到另一個線程退出阱缓。
但是屏障對象的概念更廣非凌,它們允許任意數(shù)量的線程等待,直到所 有的線程完成處理工作荆针,而線程不需要退出敞嗡。所有線程達到屏障后可以 接著工作。

可以使用 pthread_barrier_init 函數(shù)對屏障進行初始化航背,用 thread_barrier_destroy函數(shù)反初始化喉悴。

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

初始化屏障時,可以使用count參數(shù)指定玖媚,在允許所有線程繼續(xù)運
行之前箕肃,必須到達屏障的線程數(shù)目。使用attr參數(shù)指定屏障對象的屬 性今魔,我們會在下一章詳細討論∩紫瘢現(xiàn)在設置attr為NULL,用默認屬性初始 化屏障涡贱。如果使用pthread_barrier_init函數(shù)為屏障分配資源咏删,那么在反初 始化屏障時可以調(diào)用pthread_barrier_destroy函數(shù)釋放相應的資源。
可以使用pthread_barrier_wait函數(shù)來表明问词,線程已完成工作督函,準備等 所有其他線程趕上來。

#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);
返回值:若成功激挪,返回0或者 PTHREAD_BARRIER_SERIAL_THREAD;否則辰狡,返回錯誤編號
調(diào)用pthread_barrier_wait的線程在屏障計數(shù)(調(diào)用pthread_barrier_init 時設定)未滿足條件時,會進入休眠狀態(tài)垄分。如果該線程是最后一個調(diào)用 pthread_barrier_wait的線程宛篇,就滿足了屏障計數(shù),所有的線程都被喚醒薄湿。
對于一個任意線程叫倍,pthread_barrier_wait函數(shù)返回了 PTHREAD_BARRIER_SERIAL_THREAD。剩下的線程看到的返回值是 0豺瘤。這使得一個線程可以作為主線程吆倦,它可以工作在其他所有線程已完 成的工作結(jié)果上。
一旦達到屏障計數(shù)值坐求,而且線程處于非阻塞狀態(tài)蚕泽,屏障就可以被重 用。但是除非在調(diào)用了pthread_barrier_destroy函數(shù)之后桥嗤,又調(diào)用了 pthread_barrier_init函數(shù)對計數(shù)用另外的數(shù)進行初始化须妻,否則屏障計數(shù)不 會改變仔蝌。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市荒吏,隨后出現(xiàn)的幾起案子敛惊,更是在濱河造成了極大的恐慌,老刑警劉巖司倚,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件豆混,死亡現(xiàn)場離奇詭異,居然都是意外死亡动知,警方通過查閱死者的電腦和手機皿伺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盒粮,“玉大人鸵鸥,你說我怎么就攤上這事〉ぶ澹” “怎么了妒穴?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長摊崭。 經(jīng)常有香客問我讼油,道長,這世上最難降的妖魔是什么呢簸? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任矮台,我火速辦了婚禮,結(jié)果婚禮上根时,老公的妹妹穿的比我還像新娘瘦赫。我一直安慰自己,他們只是感情好蛤迎,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布确虱。 她就那樣靜靜地躺著,像睡著了一般替裆。 火紅的嫁衣襯著肌膚如雪校辩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天辆童,我揣著相機與錄音召川,去河邊找鬼。 笑死胸遇,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的汉形。 我是一名探鬼主播纸镊,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼倍阐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逗威?” 一聲冷哼從身側(cè)響起峰搪,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凯旭,沒想到半個月后概耻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了兴泥。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朵耕。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖射窒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤夯尽,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站登馒,受9級特大地震影響匙握,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陈轿,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一圈纺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧济欢,春花似錦赠堵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至半等,卻和暖如春揍愁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杀饵。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工莽囤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人切距。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓朽缎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子话肖,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 線程 在linux內(nèi)核那一部分我們知道北秽,線程其實就是一種特殊的進程,只是他們共享進程的文件和內(nèi)存等資源最筒,無論如何對...
    大雄good閱讀 663評論 0 2
  • 線程 線程的概念 典型的UNIX進程可以看成只有一個控制線程:一個進程在同一時刻只做一件事贺氓。有了多個控制線程后,在...
    ColdWave閱讀 1,453評論 0 0
  • 引用自多線程編程指南應用程序里面多個線程的存在引發(fā)了多個執(zhí)行線程安全訪問資源的潛在問題床蜘。兩個線程同時修改同一資源有...
    Mitchell閱讀 1,984評論 1 7
  • 1.內(nèi)存的頁面置換算法 (1)最佳置換算法(OPT)(理想置換算法):從主存中移出永遠不再需要的頁面;如無這樣的...
    杰倫哎呦哎呦閱讀 3,235評論 1 9
  • 線程概念 典型的UNIX進程可以看作只有一個控制線程弹囚,任務的執(zhí)行只能串行來做厨相。有了多個控制線程后,就可以同時做多個...
    pangqiu閱讀 377評論 0 2