• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            Khan's Notebook GCC/GNU/Linux Delphi/Window Java/Anywhere

            路漫漫,長(zhǎng)修遠(yuǎn),我們不能沒(méi)有錢(qián)
            隨筆 - 173, 文章 - 0, 評(píng)論 - 257, 引用 - 0
            數(shù)據(jù)加載中……

            POSIX 線程詳解(Daniel Robbins) 轉(zhuǎn)載至IBMDEV

            ?
            一種支持內(nèi)存共享的簡(jiǎn)捷工具


            Daniel Robbins (drobbins@gentoo.org), 總裁/CEO, Gentoo Technologies, Inc.

            2000 年 7 月 01 日

            ??? POSIX(可移植操作系統(tǒng)接口)線程是提高代碼響應(yīng)和性能的有力手段。在本系列中,Daniel Robbins 向您精確地展示在編程中如何使用線程。其中還涉及大量幕后細(xì)節(jié),讀完本系列文章,您完全可以運(yùn)用 POSIX 線程創(chuàng)建多線程程序。

            線程是有趣的

            了解如何正確運(yùn)用線程是每一個(gè)優(yōu)秀程序員必備的素質(zhì)。線程類(lèi)似于進(jìn)程。如同進(jìn)程,線程由內(nèi)核按時(shí)間分片進(jìn)行管理。在單處理器系統(tǒng)中,內(nèi)核使用時(shí)間分片來(lái)模擬線程的并發(fā)執(zhí)行,這種方式和進(jìn)程的相同。而在多處理器系統(tǒng)中,如同多個(gè)進(jìn)程,線程實(shí)際上一樣可以并發(fā)執(zhí)行。

            那么為什么對(duì)于大多數(shù)合作性任務(wù),多線程比多個(gè)獨(dú)立的進(jìn)程更優(yōu)越呢?這是因?yàn)椋€程共享相同的內(nèi)存空間。不同的線程可以存取內(nèi)存中的同一個(gè)變量。所以,程序中的所有線程都可以讀或?qū)懧暶鬟^(guò)的全局變量。如果曾用 fork() 編寫(xiě)過(guò)重要代碼,就會(huì)認(rèn)識(shí)到這個(gè)工具的重要性。為什么呢?雖然 fork() 允許創(chuàng)建多個(gè)進(jìn)程,但它還會(huì)帶來(lái)以下通信問(wèn)題: 如何讓多個(gè)進(jìn)程相互通信,這里每個(gè)進(jìn)程都有各自獨(dú)立的內(nèi)存空間。對(duì)這個(gè)問(wèn)題沒(méi)有一個(gè)簡(jiǎn)單的答案。雖然有許多不同種類(lèi)的本地 IPC (進(jìn)程間通信),但它們都遇到兩個(gè)重要障礙:

            ??? * 強(qiáng)加了某種形式的額外內(nèi)核開(kāi)銷(xiāo),從而降低性能。
            ??? * 對(duì)于大多數(shù)情形,IPC 不是對(duì)于代碼的“自然”擴(kuò)展。通常極大地增加了程序的復(fù)雜性。

            雙重壞事: 開(kāi)銷(xiāo)和復(fù)雜性都非好事。如果曾經(jīng)為了支持 IPC 而對(duì)程序大動(dòng)干戈過(guò),那么您就會(huì)真正欣賞線程提供的簡(jiǎn)單共享內(nèi)存機(jī)制。由于所有的線程都駐留在同一內(nèi)存空間,POSIX 線程無(wú)需進(jìn)行開(kāi)銷(xiāo)大而復(fù)雜的長(zhǎng)距離調(diào)用。只要利用簡(jiǎn)單的同步機(jī)制,程序中所有的線程都可以讀取和修改已有的數(shù)據(jù)結(jié)構(gòu)。而無(wú)需將數(shù)據(jù)經(jīng)由文件描述符轉(zhuǎn)儲(chǔ)或擠入緊窄的共享內(nèi)存空間。僅此一個(gè)原因,就足以讓您考慮應(yīng)該采用單進(jìn)程/多線程模式而非多進(jìn)程/單線程模式。






            線程是快捷的

            不僅如此。線程同樣還是非常快捷的。與標(biāo)準(zhǔn) fork() 相比,線程帶來(lái)的開(kāi)銷(xiāo)很小。內(nèi)核無(wú)需單獨(dú)復(fù)制進(jìn)程的內(nèi)存空間或文件描述符等等。這就節(jié)省了大量的 CPU 時(shí)間,使得線程創(chuàng)建比新進(jìn)程創(chuàng)建快上十到一百倍。因?yàn)檫@一點(diǎn),可以大量使用線程而無(wú)需太過(guò)于擔(dān)心帶來(lái)的 CPU 或內(nèi)存不足。使用 fork() 時(shí)導(dǎo)致的大量 CPU 占用也不復(fù)存在。這表示只要在程序中有意義,通常就可以創(chuàng)建線程。

            當(dāng)然,和進(jìn)程一樣,線程將利用多 CPU。如果軟件是針對(duì)多處理器系統(tǒng)設(shè)計(jì)的,這就真的是一大特性(如果軟件是開(kāi)放源碼,則最終可能在不少平臺(tái)上運(yùn)行)。特定類(lèi)型線程程序(尤其是 CPU 密集型程序)的性能將隨系統(tǒng)中處理器的數(shù)目幾乎線性地提高。如果正在編寫(xiě) CPU 非常密集型的程序,則絕對(duì)想設(shè)法在代碼中使用多線程。一旦掌握了線程編碼,無(wú)需使用繁瑣的 IPC 和其它復(fù)雜的通信機(jī)制,就能夠以全新和創(chuàng)造性的方法解決編碼難題。所有這些特性配合在一起使得多線程編程更有趣、快速和靈活。







            線程是可移植的

            如果熟悉 Linux 編程,就有可能知道 __clone() 系統(tǒng)調(diào)用。__clone() 類(lèi)似于 fork(),同時(shí)也有許多線程的特性。例如,使用 __clone(),新的子進(jìn)程可以有選擇地共享父進(jìn)程的執(zhí)行環(huán)境(內(nèi)存空間,文件描述符等)。這是好的一面。但 __clone() 也有不足之處。正如__clone() 在線幫助指出:

            ??? “__clone 調(diào)用是特定于 Linux 平臺(tái)的,不適用于實(shí)現(xiàn)可移植的程序。欲編寫(xiě)線程化應(yīng)用程序(多線程控制同一內(nèi)存空間),最好使用實(shí)現(xiàn) POSIX 1003.1c 線程 API 的庫(kù),例如 Linux-Threads 庫(kù)。參閱 pthread_create(3thr)。”

            雖然 __clone() 有線程的許多特性,但它是不可移植的。當(dāng)然這并不意味著代碼中不能使用它。但在軟件中考慮使用 __clone() 時(shí)應(yīng)當(dāng)權(quán)衡這一事實(shí)。值得慶幸的是,正如 __clone() 在線幫助指出,有一種更好的替代方案:POSIX 線程。如果想編寫(xiě) 可移植的 多線程代碼,代碼可運(yùn)行于 Solaris、FreeBSD、Linux 和其它平臺(tái),POSIX 線程是一種當(dāng)然之選。




            第一個(gè)線程

            下面是一個(gè) POSIX 線程的簡(jiǎn)單示例程序:


            thread1.c

            #include <pthread.h>
            #include <stdlib.h>
            #include <unistd.h>
            ?void *thread_function(void *arg) {
            ? int i;
            ? for ( i=0; i<20; i++) {
            ??? printf("Thread says hi!\n");
            ??? sleep(1);
            ? }
            ? return NULL;
            }
            int main(void) {
            ? pthread_t mythread;
            ?
            ? if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
            ??? printf("error creating thread.");
            ??? abort();
            ? }
            ? if ( pthread_join ( mythread, NULL ) ) {
            ??? printf("error joining thread.");
            ??? abort();
            ? }
            ? exit(0);
            }


            要編譯這個(gè)程序,只需先將程序存為 thread1.c,然后輸入:

            $ gcc thread1.c -o thread1 -lpthread


            運(yùn)行則輸入:

            $ ./thread1






            理解 thread1.c

            thread1.c 是一個(gè)非常簡(jiǎn)單的線程程序。雖然它沒(méi)有實(shí)現(xiàn)什么有用的功能,但可以幫助理解線程的運(yùn)行機(jī)制。下面,我們一步一步地了解這個(gè)程序是干什么的。main() 中聲明了變量 mythread,類(lèi)型是 pthread_t。pthread_t 類(lèi)型在 pthread.h 中定義,通常稱(chēng)為“線程 id”(縮寫(xiě)為 "tid")。可以認(rèn)為它是一種線程句柄。

            mythread 聲明后(記住 mythread 只是一個(gè) "tid",或是將要?jiǎng)?chuàng)建的線程的句柄),調(diào)用 pthread_create 函數(shù)創(chuàng)建一個(gè)真實(shí)活動(dòng)的線程。不要因?yàn)?pthread_create() 在 "if" 語(yǔ)句內(nèi)而受其迷惑。由于 pthread_create() 執(zhí)行成功時(shí)返回零而失敗時(shí)則返回非零值,將 pthread_create() 函數(shù)調(diào)用放在 if() 語(yǔ)句中只是為了方便地檢測(cè)失敗的調(diào)用。讓我們查看一下 pthread_create 參數(shù)。第一個(gè)參數(shù) &mythread 是指向 mythread 的指針。第二個(gè)參數(shù)當(dāng)前為 NULL,可用來(lái)定義線程的某些屬性。由于缺省的線程屬性是適用的,只需將該參數(shù)設(shè)為 NULL。

            第三個(gè)參數(shù)是新線程啟動(dòng)時(shí)調(diào)用的函數(shù)名。本例中,函數(shù)名為 thread_function()。當(dāng) thread_function() 返回時(shí),新線程將終止。本例中,線程函數(shù)沒(méi)有實(shí)現(xiàn)大的功能。它僅將 "Thread says hi!" 輸出 20 次然后退出。注意 thread_function() 接受 void * 作為參數(shù),同時(shí)返回值的類(lèi)型也是 void *。這表明可以用 void * 向新線程傳遞任意類(lèi)型的數(shù)據(jù),新線程完成時(shí)也可返回任意類(lèi)型的數(shù)據(jù)。那如何向線程傳遞一個(gè)任意參數(shù)?很簡(jiǎn)單。只要利用 pthread_create() 中的第四個(gè)參數(shù)。本例中,因?yàn)闆](méi)有必要將任何數(shù)據(jù)傳給微不足道的 thread_function(),所以將第四個(gè)參數(shù)設(shè)為 NULL。

            您也許已推測(cè)到,在 pthread_create() 成功返回之后,程序?qū)瑑蓚€(gè)線程。等一等, 兩個(gè) 線程?我們不是只創(chuàng)建了一個(gè)線程嗎?不錯(cuò),我們只創(chuàng)建了一個(gè)進(jìn)程。但是主程序同樣也是一個(gè)線程。可以這樣理解:如果編寫(xiě)的程序根本沒(méi)有使用 POSIX 線程,則該程序是單線程的(這個(gè)單線程稱(chēng)為“主”線程)。創(chuàng)建一個(gè)新線程之后程序總共就有兩個(gè)線程了。

            我想此時(shí)您至少有兩個(gè)重要問(wèn)題。第一個(gè)問(wèn)題,新線程創(chuàng)建之后主線程如何運(yùn)行。答案,主線程按順序繼續(xù)執(zhí)行下一行程序(本例中執(zhí)行 "if (pthread_join(...))")。第二個(gè)問(wèn)題,新線程結(jié)束時(shí)如何處理。答案,新線程先停止,然后作為其清理過(guò)程的一部分,等待與另一個(gè)線程合并或“連接”。

            現(xiàn)在,來(lái)看一下 pthread_join()。正如 pthread_create() 將一個(gè)線程拆分為兩個(gè), pthread_join() 將兩個(gè)線程合并為一個(gè)線程。pthread_join() 的第一個(gè)參數(shù)是 tid mythread。第二個(gè)參數(shù)是指向 void 指針的指針。如果 void 指針不為 NULL,pthread_join 將線程的 void * 返回值放置在指定的位置上。由于我們不必理會(huì) thread_function() 的返回值,所以將其設(shè)為 NULL.

            您會(huì)注意到 thread_function() 花了 20 秒才完成。在 thread_function() 結(jié)束很久之前,主線程就已經(jīng)調(diào)用了 pthread_join()。如果發(fā)生這種情況,主線程將中斷(轉(zhuǎn)向睡眠)然后等待 thread_function() 完成。當(dāng) thread_function() 完成后, pthread_join() 將返回。這時(shí)程序又只有一個(gè)主線程。當(dāng)程序退出時(shí),所有新線程已經(jīng)使用 pthread_join() 合并了。這就是應(yīng)該如何處理在程序中創(chuàng)建的每個(gè)新線程的過(guò)程。如果沒(méi)有合并一個(gè)新線程,則它仍然對(duì)系統(tǒng)的最大線程數(shù)限制不利。這意味著如果未對(duì)線程做正確的清理,最終會(huì)導(dǎo)致 pthread_create() 調(diào)用失敗。





            無(wú)父,無(wú)子

            如果使用過(guò) fork() 系統(tǒng)調(diào)用,可能熟悉父進(jìn)程和子進(jìn)程的概念。當(dāng)用 fork() 創(chuàng)建另一個(gè)新進(jìn)程時(shí),新進(jìn)程是子進(jìn)程,原始進(jìn)程是父進(jìn)程。這創(chuàng)建了可能非常有用的層次關(guān)系,尤其是等待子進(jìn)程終止時(shí)。例如,waitpid() 函數(shù)讓當(dāng)前進(jìn)程等待所有子進(jìn)程終止。waitpid() 用來(lái)在父進(jìn)程中實(shí)現(xiàn)簡(jiǎn)單的清理過(guò)程。

            而 POSIX 線程就更有意思。您可能已經(jīng)注意到我一直有意避免使用“父線程”和“子線程”的說(shuō)法。這是因?yàn)?POSIX 線程中不存在這種層次關(guān)系。雖然主線程可以創(chuàng)建一個(gè)新線程,新線程可以創(chuàng)建另一個(gè)新線程,POSIX 線程標(biāo)準(zhǔn)將它們視為等同的層次。所以等待子線程退出的概念在這里沒(méi)有意義。POSIX 線程標(biāo)準(zhǔn)不記錄任何“家族”信息。缺少家族信息有一個(gè)主要含意:如果要等待一個(gè)線程終止,就必須將線程的 tid 傳遞給 pthread_join()。線程庫(kù)無(wú)法為您斷定 tid。

            對(duì)大多數(shù)開(kāi)發(fā)者來(lái)說(shuō)這不是個(gè)好消息,因?yàn)檫@會(huì)使有多個(gè)線程的程序復(fù)雜化。不過(guò)不要為此擔(dān)憂。POSIX 線程標(biāo)準(zhǔn)提供了有效地管理多個(gè)線程所需要的所有工具。實(shí)際上,沒(méi)有父/子關(guān)系這一事實(shí)卻為在程序中使用線程開(kāi)辟了更創(chuàng)造性的方法。例如,如果有一個(gè)線程稱(chēng)為線程 1,線程 1 創(chuàng)建了稱(chēng)為線程 2 的線程,則線程 1 自己沒(méi)有必要調(diào)用 pthread_join() 來(lái)合并線程 2,程序中其它任一線程都可以做到。當(dāng)編寫(xiě)大量使用線程的代碼時(shí),這就可能允許發(fā)生有趣的事情。例如,可以創(chuàng)建一個(gè)包含所有已停止線程的全局“死線程列表”,然后讓一個(gè)專(zhuān)門(mén)的清理線程專(zhuān)等停止的線程加到列表中。這個(gè)清理線程調(diào)用 pthread_join() 將剛停止的線程與自己合并。現(xiàn)在,僅用一個(gè)線程就巧妙和有效地處理了全部清理。





            同步漫游

            現(xiàn)在我們來(lái)看一些代碼,這些代碼做了一些意想不到的事情。thread2.c 的代碼如下:


            thread2.c

            #include <pthread.h>
            #include <stdlib.h>
            #include <unistd.h>
            #include <stdio.h>
            int myglobal;
            ?void *thread_function(void *arg) {
            ? int i,j;
            ? for ( i=0; i<20; i++) {
            ??? j=myglobal;
            ??? j=j+1;
            ??? printf(".");
            ??? fflush(stdout);
            ??? sleep(1);
            ??? myglobal=j;
            ? }
            ? return NULL;
            }
            int main(void) {
            ? pthread_t mythread;
            ? int i;
            ? if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
            ??? printf("error creating thread.");
            ??? abort();
            ? }
            ? for ( i=0; i<20; i++) {
            ??? myglobal=myglobal+1;
            ??? printf("o");
            ??? fflush(stdout);
            ??? sleep(1);
            ? }
            ? if ( pthread_join ( mythread, NULL ) ) {
            ??? printf("error joining thread.");
            ??? abort();
            ? }
            ? printf("\nmyglobal equals %d\n",myglobal);
            ? exit(0);
            }







            理解 thread2.c

            如同第一個(gè)程序,這個(gè)程序創(chuàng)建一個(gè)新線程。主線程和新線程都將全局變量 myglobal 加一 20 次。但是程序本身產(chǎn)生了某些意想不到的結(jié)果。編譯代碼請(qǐng)輸入:

            $ gcc thread2.c -o thread2 -lpthread


            運(yùn)行請(qǐng)輸入:

            $ ./thread2


            輸出:

            $ ./thread2
            ..o.o.o.o.oo.o.o.o.o.o.o.o.o.o..o.o.o.o.o
            myglobal equals 21


            非常意外吧!因?yàn)?myglobal 從零開(kāi)始,主線程和新線程各自對(duì)其進(jìn)行了 20 次加一, 程序結(jié)束時(shí) myglobal 值應(yīng)當(dāng)?shù)扔?40。由于 myglobal 輸出結(jié)果為 21,這其中肯定有問(wèn)題。但是究竟是什么呢?

            放棄嗎?好,讓我來(lái)解釋是怎么一回事。首先查看函數(shù) thread_function()。注意如何將 myglobal 復(fù)制到局部變量 "j" 了嗎? 接著將 j 加一, 再睡眠一秒,然后到這時(shí)才將新的 j 值復(fù)制到 myglobal?這就是關(guān)鍵所在。設(shè)想一下,如果主線程就在新線程將 myglobal 值復(fù)制給 j 后 立即將 myglobal 加一,會(huì)發(fā)生什么?當(dāng) thread_function() 將 j 的值寫(xiě)回 myglobal 時(shí),就覆蓋了主線程所做的修改。

            當(dāng)編寫(xiě)線程程序時(shí),應(yīng)避免產(chǎn)生這種無(wú)用的副作用,否則只會(huì)浪費(fèi)時(shí)間(當(dāng)然,除了編寫(xiě)關(guān)于 POSIX 線程的文章時(shí)有用)。那么,如何才能排除這種問(wèn)題呢?

            由于是將 myglobal 復(fù)制給 j 并且等了一秒之后才寫(xiě)回時(shí)產(chǎn)生問(wèn)題,可以嘗試避免使用臨時(shí)局部變量并直接將 myglobal 加一。雖然這種解決方案對(duì)這個(gè)特定例子適用,但它還是不正確。如果我們對(duì) myglobal 進(jìn)行相對(duì)復(fù)雜的數(shù)學(xué)運(yùn)算,而不是簡(jiǎn)單的加一,這種方法就會(huì)失效。但是為什么呢?

            要理解這個(gè)問(wèn)題,必須記住線程是并發(fā)運(yùn)行的。即使在單處理器系統(tǒng)上運(yùn)行(內(nèi)核利用時(shí)間分片模擬多任務(wù))也是可以的,從程序員的角度,想像兩個(gè)線程是同時(shí)執(zhí)行的。thread2.c 出現(xiàn)問(wèn)題是因?yàn)?thread_function() 依賴以下論據(jù):在 myglobal 加一之前的大約一秒鐘期間不會(huì)修改 myglobal。需要有些途徑讓一個(gè)線程在對(duì) myglobal 做更改時(shí)通知其它線程“不要靠近”。我將在下一篇文章中講解如何做到這一點(diǎn)。到時(shí)候見(jiàn)。




            通用線程:POSIX 線程詳解,第 2部分

            稱(chēng)作互斥對(duì)象的小玩意

            ?? ?

            Daniel Robbins (drobbins@gentoo.org), 總裁/CEO, Gentoo Technologies, Inc.

            2000 年 8 月 01 日

            ??? POSIX 線程是提高代碼響應(yīng)和性能的有力手段。在此三部分系列文章的第二篇中,Daniel Robbins 將說(shuō)明,如何使用被稱(chēng)為互斥對(duì)象的靈巧小玩意,來(lái)保護(hù)線程代碼中共享數(shù)據(jù)結(jié)構(gòu)的完整性。

            互斥我吧!

            在 前一篇文章中 ,談到了會(huì)導(dǎo)致異常結(jié)果的線程代碼。兩個(gè)線程分別對(duì)同一個(gè)全局變量進(jìn)行了二十次加一。變量的值最后應(yīng)該是 40,但最終值卻是 21。這是怎么回事呢?因?yàn)橐粋€(gè)線程不停地“取消”了另一個(gè)線程執(zhí)行的加一操作,所以產(chǎn)生這個(gè)問(wèn)題。現(xiàn)在讓我們來(lái)查看改正后的代碼,它使用 互斥對(duì)象(mutex)來(lái)解決該問(wèn)題:


            thread3.c

            #include <pthread.h>
            #include <stdlib.h>
            #include <unistd.h>
            #include <stdio.h>
            int myglobal;
            pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
            ?void *thread_function(void *arg) {
            ? int i,j;
            ? for ( i=0; i<20; i++) {
            ??? pthread_mutex_lock(&mymutex);
            ??? j=myglobal;
            ??? j=j+1;
            ??? printf(".");
            ??? fflush(stdout);
            ??? sleep(1);
            ??? myglobal=j;
            ??? pthread_mutex_unlock(&mymutex);
            ? }
            ? return NULL;
            }
            int main(void) {
            ? pthread_t mythread;
            ? int i;
            ? if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
            ??? printf("error creating thread.");
            ??? abort();
            ? }
            ? for ( i=0; i<20; i++) {
            ??? pthread_mutex_lock(&mymutex);
            ??? myglobal=myglobal+1;
            ??? pthread_mutex_unlock(&mymutex);
            ??? printf("o");
            ??? fflush(stdout);
            ??? sleep(1);
            ? }
            ? if ( pthread_join ( mythread, NULL ) ) {
            ??? printf("error joining thread.");
            ??? abort();
            ? }
            ? printf("\nmyglobal equals %d\n",myglobal);
            ? exit(0);
            }







            解讀一下

            如果將這段代碼與 前一篇文章 中給出的版本作一個(gè)比較,就會(huì)注意到增加了 pthread_mutex_lock() 和 pthread_mutex_unlock() 函數(shù)調(diào)用。在線程程序中這些調(diào)用執(zhí)行了不可或缺的功能。他們提供了一種 相互排斥的方法(互斥對(duì)象即由此得名)。兩個(gè)線程不能同時(shí)對(duì)同一個(gè)互斥對(duì)象加鎖。

            互斥對(duì)象是這樣工作的。如果線程 a 試圖鎖定一個(gè)互斥對(duì)象,而此時(shí)線程 b 已鎖定了同一個(gè)互斥對(duì)象時(shí),線程 a 就將進(jìn)入睡眠狀態(tài)。一旦線程 b 釋放了互斥對(duì)象(通過(guò) pthread_mutex_unlock() 調(diào)用),線程 a 就能夠鎖定這個(gè)互斥對(duì)象(換句話說(shuō),線程 a 就將從 pthread_mutex_lock() 函數(shù)調(diào)用中返回,同時(shí)互斥對(duì)象被鎖定)。同樣地,當(dāng)線程 a 正鎖定互斥對(duì)象時(shí),如果線程 c 試圖鎖定互斥對(duì)象的話,線程 c 也將臨時(shí)進(jìn)入睡眠狀態(tài)。對(duì)已鎖定的互斥對(duì)象上調(diào)用 pthread_mutex_lock() 的所有線程都將進(jìn)入睡眠狀態(tài),這些睡眠的線程將“排隊(duì)”訪問(wèn)這個(gè)互斥對(duì)象。

            通常使用 pthread_mutex_lock() 和 pthread_mutex_unlock() 來(lái)保護(hù)數(shù)據(jù)結(jié)構(gòu)。這就是說(shuō),通過(guò)線程的鎖定和解鎖,對(duì)于某一數(shù)據(jù)結(jié)構(gòu),確保某一時(shí)刻只能有一個(gè)線程能夠訪問(wèn)它。可以推測(cè)到,當(dāng)線程試圖鎖定一個(gè)未加鎖的互斥對(duì)象時(shí),POSIX 線程庫(kù)將同意鎖定,而不會(huì)使線程進(jìn)入睡眠狀態(tài)。


            請(qǐng)看這幅輕松的漫畫(huà),四個(gè)小精靈重現(xiàn)了最近一次 pthread_mutex_lock() 調(diào)用的一個(gè)場(chǎng)面。

            圖中,鎖定了互斥對(duì)象的線程能夠存取復(fù)雜的數(shù)據(jù)結(jié)構(gòu),而不必?fù)?dān)心同時(shí)會(huì)有其它線程干擾。那個(gè)數(shù)據(jù)結(jié)構(gòu)實(shí)際上是“凍結(jié)”了,直到互斥對(duì)象被解鎖為止。pthread_mutex_lock() 和 pthread_mutex_unlock() 函數(shù)調(diào)用,如同“在施工中”標(biāo)志一樣,將正在修改和讀取的某一特定共享數(shù)據(jù)包圍起來(lái)。這兩個(gè)函數(shù)調(diào)用的作用就是警告其它線程,要它們繼續(xù)睡眠并等待輪到它們對(duì)互斥對(duì)象加鎖。當(dāng)然,除非在 每個(gè) 對(duì)特定數(shù)據(jù)結(jié)構(gòu)進(jìn)行讀寫(xiě)操作的語(yǔ)句前后,都分別放上 pthread_mutex_lock() 和 pthread_mutext_unlock() 調(diào)用,才會(huì)出現(xiàn)這種情況。





            為什么要用互斥對(duì)象?

            聽(tīng)上去很有趣,但究竟為什么要讓線程睡眠呢?要知道,線程的主要優(yōu)點(diǎn)不就是其具有獨(dú)立工作、更多的時(shí)候是同時(shí)工作的能力嗎?是的,確實(shí)是這樣。然而,每個(gè)重要的線程程序都需要使用某些互斥對(duì)象。讓我們?cè)倏匆幌率纠绦蛞员憷斫庠蛩凇?br />
            請(qǐng)看 thread_function(),循環(huán)中一開(kāi)始就鎖定了互斥對(duì)象,最后才將它解鎖。在這個(gè)示例程序中,mymutex 用來(lái)保護(hù) myglobal 的值。仔細(xì)查看 thread_function(),加一代碼把 myglobal 復(fù)制到一個(gè)局部變量,對(duì)局部變量加一,睡眠一秒鐘,在這之后才把局部變量的值傳回給 myglobal。不使用互斥對(duì)象時(shí),即使主線程在 thread_function() 線程睡眠一秒鐘期間內(nèi)對(duì) myglobal 加一,thread_function() 蘇醒后也會(huì)覆蓋主線程所加的值。使用互斥對(duì)象能夠保證這種情形不會(huì)發(fā)生。(您也許會(huì)想到,我增加了一秒鐘延遲以觸發(fā)不正確的結(jié)果。把局部變量的值賦給 myglobal 之前,實(shí)際上沒(méi)有什么真正理由要求 thread_function() 睡眠一秒鐘。)使用互斥對(duì)象的新程序產(chǎn)生了期望的結(jié)果:

            $ ./thread3
            o..o..o.o..o..o.o.o.o.o..o..o..o.ooooooo
            myglobal equals 40


            為了進(jìn)一步探索這個(gè)極為重要的概念,讓我們看一看程序中進(jìn)行加一操作的代碼:

            thread_function() 加一代碼:
            ??? j=myglobal;
            ??? j=j+1;
            ??? printf(".");
            ??? fflush(stdout);
            ??? sleep(1);
            ??? myglobal=j;
            主線程加一代碼:
            ??? myglobal=myglobal+1;


            如果代碼是位于單線程程序中,可以預(yù)期 thread_function() 代碼將完整執(zhí)行。接下來(lái)才會(huì)執(zhí)行主線程代碼(或者是以相反的順序執(zhí)行)。在不使用互斥對(duì)象的線程程序中,代碼可能(幾乎是,由于調(diào)用了 sleep() 的緣故)以如下的順序執(zhí)行:

            ??? thread_function() 線程??????? 主線程
            ??? j=myglobal;
            ??? j=j+1;
            ??? printf(".");
            ??? fflush(stdout);
            ??? sleep(1);???????????????????? myglobal=myglobal+1;
            ??? myglobal=j;


            當(dāng)代碼以此特定順序執(zhí)行時(shí),將覆蓋主線程對(duì) myglobal 的修改。程序結(jié)束后,就將得到不正確的值。如果是在操縱指針的話,就可能產(chǎn)生段錯(cuò)誤。注意到 thread_function() 線程按順序執(zhí)行了它的所有指令。看來(lái)不象是 thread_function() 有什么次序顛倒。問(wèn)題是,同一時(shí)間內(nèi),另一個(gè)線程對(duì)同一數(shù)據(jù)結(jié)構(gòu)進(jìn)行了另一個(gè)修改。





            線程內(nèi)幕 1

            在解釋如何確定在何處使用互斥對(duì)象之前,先來(lái)深入了解一下線程的內(nèi)部工作機(jī)制。請(qǐng)看第一個(gè)例子:

            假設(shè)主線程將創(chuàng)建三個(gè)新線程:線程 a、線程 b 和線程 c。假定首先創(chuàng)建線程 a,然后是線程 b,最后創(chuàng)建線程 c。

            ??? pthread_create( &thread_a, NULL, thread_function, NULL);
            ??? pthread_create( &thread_b, NULL, thread_function, NULL);
            ??? pthread_create( &thread_c, NULL, thread_function, NULL);


            在第一個(gè) pthread_create() 調(diào)用完成后,可以假定線程 a 不是已存在就是已結(jié)束并停止。第二個(gè) pthread_create() 調(diào)用后,主線程和線程 b 都可以假定線程 a 存在(或已停止)。

            然而,就在第二個(gè) create() 調(diào)用返回后,主線程無(wú)法假定是哪一個(gè)線程(a 或 b)會(huì)首先開(kāi)始運(yùn)行。雖然兩個(gè)線程都已存在,線程 CPU 時(shí)間片的分配取決于內(nèi)核和線程庫(kù)。至于誰(shuí)將首先運(yùn)行,并沒(méi)有嚴(yán)格的規(guī)則。盡管線程 a 更有可能在線程 b 之前開(kāi)始執(zhí)行,但這并無(wú)保證。對(duì)于多處理器系統(tǒng),情況更是如此。如果編寫(xiě)的代碼假定在線程 b 開(kāi)始執(zhí)行之前實(shí)際上執(zhí)行線程 a 的代碼,那么,程序最終正確運(yùn)行的概率是 99%。或者更糟糕,程序在您的機(jī)器上 100% 地正確運(yùn)行,而在您客戶的四處理器服務(wù)器上正確運(yùn)行的概率卻是零。

            從這個(gè)例子還可以得知,線程庫(kù)保留了每個(gè)單獨(dú)線程的代碼執(zhí)行順序。換句話說(shuō),實(shí)際上那三個(gè) pthread_create() 調(diào)用將按它們出現(xiàn)的順序執(zhí)行。從主線程上來(lái)看,所有代碼都是依次執(zhí)行的。有時(shí),可以利用這一點(diǎn)來(lái)優(yōu)化部分線程程序。例如,在上例中,線程 c 就可以假定線程 a 和線程 b 不是正在運(yùn)行就是已經(jīng)終止。它不必?fù)?dān)心存在還沒(méi)有創(chuàng)建線程 a 和線程 b 的可能性。可以使用這一邏輯來(lái)優(yōu)化線程程序。





            線程內(nèi)幕 2

            現(xiàn)在來(lái)看另一個(gè)假想的例子。假設(shè)有許多線程,他們都正在執(zhí)行下列代碼:

            ??? myglobal=myglobal+1;


            那么,是否需要在加一操作語(yǔ)句前后分別鎖定和解鎖互斥對(duì)象呢?也許有人會(huì)說(shuō)“不”。編譯器極有可能把上述賦值語(yǔ)句編譯成一條機(jī)器指令。大家都知道,不可能"半途"中斷一條機(jī)器指令。即使是硬件中斷也不會(huì)破壞機(jī)器指令的完整性。基于以上考慮,很可能傾向于完全省略 pthread_mutex_lock() 和 pthread_mutex_unlock() 調(diào)用。不要這樣做。

            我在說(shuō)廢話嗎?不完全是這樣。首先,不應(yīng)該假定上述賦值語(yǔ)句一定會(huì)被編譯成一條機(jī)器指令,除非親自驗(yàn)證了機(jī)器代碼。即使插入某些內(nèi)嵌匯編語(yǔ)句以確保加一操作的完整執(zhí)行――甚至,即使是自己動(dòng)手寫(xiě)編譯器!-- 仍然可能有問(wèn)題。

            答案在這里。使用單條內(nèi)嵌匯編操作碼在單處理器系統(tǒng)上可能不會(huì)有什么問(wèn)題。每個(gè)加一操作都將完整地進(jìn)行,并且多半會(huì)得到期望的結(jié)果。但是多處理器系統(tǒng)則截然不同。在多 CPU 機(jī)器上,兩個(gè)單獨(dú)的處理器可能會(huì)在幾乎同一時(shí)刻(或者,就在同一時(shí)刻)執(zhí)行上述賦值語(yǔ)句。不要忘了,這時(shí)對(duì)內(nèi)存的修改需要先從 L1 寫(xiě)入 L2 高速緩存、然后才寫(xiě)入主存。(SMP 機(jī)器并不只是增加了處理器而已;它還有用來(lái)仲裁對(duì) RAM 存取的特殊硬件。)最終,根本無(wú)法搞清在寫(xiě)入主存的競(jìng)爭(zhēng)中,哪個(gè) CPU 將會(huì)"勝出"。要產(chǎn)生可預(yù)測(cè)的代碼,應(yīng)使用互斥對(duì)象。互斥對(duì)象將插入一道"內(nèi)存關(guān)卡",由它來(lái)確保對(duì)主存的寫(xiě)入按照線程鎖定互斥對(duì)象的順序進(jìn)行。

            考慮一種以 32 位塊為單位更新主存的 SMP 體系結(jié)構(gòu)。如果未使用互斥對(duì)象就對(duì)一個(gè) 64 位整數(shù)進(jìn)行加一操作,整數(shù)的最高 4 位字節(jié)可能來(lái)自一個(gè) CPU,而其它 4 個(gè)字節(jié)卻來(lái)自另一 CPU。糟糕吧!最糟糕的是,使用差勁的技術(shù),您的程序在重要客戶的系統(tǒng)上有可能不是很長(zhǎng)時(shí)間才崩潰一次,就是早上三點(diǎn)鐘就崩潰。David R. Butenhof 在他的《POSIX 線程編程》(請(qǐng)參閱本文末尾的 參考資料部分)一書(shū)中,討論了由于未使用互斥對(duì)象而將產(chǎn)生的種種情況。






            許多互斥對(duì)象

            如果放置了過(guò)多的互斥對(duì)象,代碼就沒(méi)有什么并發(fā)性可言,運(yùn)行起來(lái)也比單線程解決方案慢。如果放置了過(guò)少的互斥對(duì)象,代碼將出現(xiàn)奇怪和令人尷尬的錯(cuò)誤。幸運(yùn)的是,有一個(gè)中間立場(chǎng)。首先,互斥對(duì)象是用于串行化存取*共享數(shù)據(jù)*。不要對(duì)非共享數(shù)據(jù)使用互斥對(duì)象,并且,如果程序邏輯確保任何時(shí)候都只有一個(gè)線程能存取特定數(shù)據(jù)結(jié)構(gòu),那么也不要使用互斥對(duì)象。

            其次,如果要使用共享數(shù)據(jù),那么在讀、寫(xiě)共享數(shù)據(jù)時(shí)都應(yīng)使用互斥對(duì)象。用 pthread_mutex_lock() 和 pthread_mutex_unlock() 把讀寫(xiě)部分保護(hù)起來(lái),或者在程序中不固定的地方隨機(jī)使用它們。學(xué)會(huì)從一個(gè)線程的角度來(lái)審視代碼,并確保程序中每一個(gè)線程對(duì)內(nèi)存的觀點(diǎn)都是一致和合適的。為了熟悉互斥對(duì)象的用法,最初可能要花好幾個(gè)小時(shí)來(lái)編寫(xiě)代碼,但是很快就會(huì)習(xí)慣并且*也*不必多想就能夠正確使用它們。




            使用調(diào)用:初始化

            現(xiàn)在該來(lái)看看使用互斥對(duì)象的各種不同方法了。讓我們從初始化開(kāi)始。在 thread3.c 示例 中,我們使用了靜態(tài)初始化方法。這需要聲明一個(gè) pthread_mutex_t 變量,并賦給它常數(shù) PTHREAD_MUTEX_INITIALIZER:

            pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;


            很簡(jiǎn)單吧。但是還可以動(dòng)態(tài)地創(chuàng)建互斥對(duì)象。當(dāng)代碼使用 malloc() 分配一個(gè)新的互斥對(duì)象時(shí),使用這種動(dòng)態(tài)方法。此時(shí),靜態(tài)初始化方法是行不通的,并且應(yīng)當(dāng)使用例程 pthread_mutex_init():

            int pthread_mutex_init( pthread_mutex_t *mymutex, const pthread_mutexattr_t *attr)


            正如所示,pthread_mutex_init 接受一個(gè)指針作為參數(shù)以初始化為互斥對(duì)象,該指針指向一塊已分配好的內(nèi)存區(qū)。第二個(gè)參數(shù),可以接受一個(gè)可選的 pthread_mutexattr_t 指針。這個(gè)結(jié)構(gòu)可用來(lái)設(shè)置各種互斥對(duì)象屬性。但是通常并不需要這些屬性,所以正常做法是指定 NULL。

            一旦使用 pthread_mutex_init() 初始化了互斥對(duì)象,就應(yīng)使用 pthread_mutex_destroy() 消除它。pthread_mutex_destroy() 接受一個(gè)指向 pthread_mutext_t 的指針作為參數(shù),并釋放創(chuàng)建互斥對(duì)象時(shí)分配給它的任何資源。請(qǐng)注意, pthread_mutex_destroy() 不會(huì) 釋放用來(lái)存儲(chǔ) pthread_mutex_t 的內(nèi)存。釋放自己的內(nèi)存完全取決于您。還必須注意一點(diǎn),pthread_mutex_init() 和 pthread_mutex_destroy() 成功時(shí)都返回零。





            使用調(diào)用:鎖定

            pthread_mutex_lock(pthread_mutex_t *mutex)


            pthread_mutex_lock() 接受一個(gè)指向互斥對(duì)象的指針作為參數(shù)以將其鎖定。如果碰巧已經(jīng)鎖定了互斥對(duì)象,調(diào)用者將進(jìn)入睡眠狀態(tài)。函數(shù)返回時(shí),將喚醒調(diào)用者(顯然)并且調(diào)用者還將保留該鎖。函數(shù)調(diào)用成功時(shí)返回零,失敗時(shí)返回非零的錯(cuò)誤代碼。

            pthread_mutex_unlock(pthread_mutex_t *mutex)


            pthread_mutex_unlock() 與 pthread_mutex_lock() 相配合,它把線程已經(jīng)加鎖的互斥對(duì)象解鎖。始終應(yīng)該盡快對(duì)已加鎖的互斥對(duì)象進(jìn)行解鎖(以提高性能)。并且絕對(duì)不要對(duì)您未保持鎖的互斥對(duì)象進(jìn)行解鎖操作(否則,pthread_mutex_unlock() 調(diào)用將失敗并帶一個(gè)非零的 EPERM 返回值)。

            pthread_mutex_trylock(pthread_mutex_t *mutex)


            當(dāng)線程正在做其它事情的時(shí)候(由于互斥對(duì)象當(dāng)前是鎖定的),如果希望鎖定互斥對(duì)象,這個(gè)調(diào)用就相當(dāng)方便。調(diào)用 pthread_mutex_trylock() 時(shí)將嘗試鎖定互斥對(duì)象。如果互斥對(duì)象當(dāng)前處于解鎖狀態(tài),那么您將獲得該鎖并且函數(shù)將返回零。然而,如果互斥對(duì)象已鎖定,這個(gè)調(diào)用也不會(huì)阻塞。當(dāng)然,它會(huì)返回非零的 EBUSY 錯(cuò)誤值。然后可以繼續(xù)做其它事情,稍后再嘗試鎖定。




            等待條件發(fā)生

            互斥對(duì)象是線程程序必需的工具,但它們并非萬(wàn)能的。例如,如果線程正在等待共享數(shù)據(jù)內(nèi)某個(gè)條件出現(xiàn),那會(huì)發(fā)生什么呢?代碼可以反復(fù)對(duì)互斥對(duì)象鎖定和解鎖,以檢查值的任何變化。同時(shí),還要快速將互斥對(duì)象解鎖,以便其它線程能夠進(jìn)行任何必需的更改。這是一種非常可怕的方法,因?yàn)榫€程需要在合理的時(shí)間范圍內(nèi)頻繁地循環(huán)檢測(cè)變化。

            在每次檢查之間,可以讓調(diào)用線程短暫地進(jìn)入睡眠,比如睡眠三秒鐘,但是因此線程代碼就無(wú)法最快作出響應(yīng)。真正需要的是這樣一種方法,當(dāng)線程在等待滿足某些條件時(shí)使線程進(jìn)入睡眠狀態(tài)。一旦條件滿足,還需要一種方法以喚醒因等待滿足特定條件而睡眠的線程。如果能夠做到這一點(diǎn),線程代碼將是非常高效的,并且不會(huì)占用寶貴的互斥對(duì)象鎖。這正是 POSIX 條件變量能做的事!

            而 POSIX 條件變量將是我下一篇文章的主題,其中將說(shuō)明如何正確使用條件變量。到那時(shí),您將擁有了創(chuàng)建復(fù)雜線程程序所需的全部資源,那些線程程序可以模擬工作人員、裝配線等等。既然您已經(jīng)越來(lái)越熟悉線程,我將在下一篇文章中加快進(jìn)度。這樣,在下一篇文章的結(jié)尾就能放上一個(gè)相對(duì)復(fù)雜的線程程序。說(shuō)到等到條件產(chǎn)生,下次再見(jiàn)!





            通用線程:POSIX 線程詳解,第 3 部分

            使用條件變量提高效率
            ?? ?


            Daniel Robbins, 總裁兼 CEO, Gentoo Technologies, Inc.

            2000 年 9 月 01 日

            ??? 本文是 POSIX 線程三部曲系列的最后一部分,Daniel 將詳細(xì)討論如何使用條件變量。條件變量是 POSIX 線程結(jié)構(gòu),可以讓您在遇到某些條件時(shí)“喚醒”線程。可以將它們看作是一種線程安全的信號(hào)發(fā)送。Daniel 使用目前您所學(xué)到的知識(shí)實(shí)現(xiàn)了一個(gè)多線程工作組應(yīng)用程序,本文將圍繞著這一示例而進(jìn)行討論。

            條件變量詳解

            在 上一篇文章結(jié)束時(shí),我描述了一個(gè)比較特殊的難題:如果線程正在等待某個(gè)特定條件發(fā)生,它應(yīng)該如何處理這種情況?它可以重復(fù)對(duì)互斥對(duì)象鎖定和解鎖,每次都會(huì)檢查共享數(shù)據(jù)結(jié)構(gòu),以查找某個(gè)值。但這是在浪費(fèi)時(shí)間和資源,而且這種繁忙查詢的效率非常低。解決這個(gè)問(wèn)題的最佳方法是使用 pthread_cond_wait() 調(diào)用來(lái)等待特殊條件發(fā)生。

            了解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 線程信號(hào)發(fā)送系統(tǒng)的核心,也是最難以理解的部分。

            首先,讓我們考慮以下情況:線程為查看已鏈接列表而鎖定了互斥對(duì)象,然而該列表恰巧是空的。這一特定線程什么也干不了 -- 其設(shè)計(jì)意圖是從列表中除去節(jié)點(diǎn),但是現(xiàn)在卻沒(méi)有節(jié)點(diǎn)。因此,它只能:

            鎖定互斥對(duì)象時(shí),線程將調(diào)用 pthread_cond_wait(&mycond,&mymutex)。pthread_cond_wait() 調(diào)用相當(dāng)復(fù)雜,因此我們每次只執(zhí)行它的一個(gè)操作。

            pthread_cond_wait() 所做的第一件事就是同時(shí)對(duì)互斥對(duì)象解鎖(于是其它線程可以修改已鏈接列表),并等待條件 mycond 發(fā)生(這樣當(dāng) pthread_cond_wait() 接收到另一個(gè)線程的“信號(hào)”時(shí),它將蘇醒)。現(xiàn)在互斥對(duì)象已被解鎖,其它線程可以訪問(wèn)和修改已鏈接列表,可能還會(huì)添加項(xiàng)。

            此時(shí),pthread_cond_wait() 調(diào)用還未返回。對(duì)互斥對(duì)象解鎖會(huì)立即發(fā)生,但等待條件 mycond 通常是一個(gè)阻塞操作,這意味著線程將睡眠,在它蘇醒之前不會(huì)消耗 CPU 周期。這正是我們期待發(fā)生的情況。線程將一直睡眠,直到特定條件發(fā)生,在這期間不會(huì)發(fā)生任何浪費(fèi) CPU 時(shí)間的繁忙查詢。從線程的角度來(lái)看,它只是在等待 pthread_cond_wait() 調(diào)用返回。

            現(xiàn)在繼續(xù)說(shuō)明,假設(shè)另一個(gè)線程(稱(chēng)作“2 號(hào)線程”)鎖定了 mymutex 并對(duì)已鏈接列表添加了一項(xiàng)。在對(duì)互斥對(duì)象解鎖之后,2 號(hào)線程會(huì)立即調(diào)用函數(shù) pthread_cond_broadcast(&mycond)。此操作之后,2 號(hào)線程將使所有等待 mycond 條件變量的線程立即蘇醒。這意味著第一個(gè)線程(仍處于 pthread_cond_wait() 調(diào)用中)現(xiàn)在將蘇醒。

            現(xiàn)在,看一下第一個(gè)線程發(fā)生了什么。您可能會(huì)認(rèn)為在 2 號(hào)線程調(diào)用 pthread_cond_broadcast(&mymutex) 之后,1 號(hào)線程的 pthread_cond_wait() 會(huì)立即返回。不是那樣!實(shí)際上,pthread_cond_wait() 將執(zhí)行最后一個(gè)操作:重新鎖定 mymutex。一旦 pthread_cond_wait() 鎖定了互斥對(duì)象,那么它將返回并允許 1 號(hào)線程繼續(xù)執(zhí)行。那時(shí),它可以馬上檢查列表,查看它所感興趣的更改。




            停止并回顧!

            那個(gè)過(guò)程非常復(fù)雜,因此讓我們先來(lái)回顧一下。第一個(gè)線程首先調(diào)用:

            ??? pthread_mutex_lock(&mymutex);


            然后,它檢查了列表。沒(méi)有找到感興趣的東西,于是它調(diào)用:

            ??? pthread_cond_wait(&mycond, &mymutex);


            然后,pthread_cond_wait() 調(diào)用在返回前執(zhí)行許多操作:

            ????? ?
            ??? pthread_mutex_unlock(&mymutex);


            它對(duì) mymutex 解鎖,然后進(jìn)入睡眠狀態(tài),等待 mycond 以接收 POSIX 線程“信號(hào)”。一旦接收到“信號(hào)”(加引號(hào)是因?yàn)槲覀儾⒉皇窃谟懻搨鹘y(tǒng)的 UNIX 信號(hào),而是來(lái)自 pthread_cond_signal() 或 pthread_cond_broadcast() 調(diào)用的信號(hào)),它就會(huì)蘇醒。但 pthread_cond_wait() 沒(méi)有立即返回 -- 它還要做一件事:重新鎖定 mutex:

            ??? ?
            ??? pthread_mutex_lock(&mymutex);


            pthread_cond_wait() 知道我們?cè)诓檎?mymutex “背后”的變化,因此它繼續(xù)操作,為我們鎖定互斥對(duì)象,然后才返回。




            pthread_cond_wait() 小測(cè)驗(yàn)

            現(xiàn)在已回顧了 pthread_cond_wait() 調(diào)用,您應(yīng)該了解了它的工作方式。應(yīng)該能夠敘述 pthread_cond_wait() 依次執(zhí)行的所有操作。嘗試一下。如果理解了 pthread_cond_wait(),其余部分就相當(dāng)容易,因此請(qǐng)重新閱讀以上部分,直到記住為止。好,讀完之后,能否告訴我在調(diào)用 pthread_cond_wait() 之 前,互斥對(duì)象必須處于什么狀態(tài)?pthread_cond_wait() 調(diào)用返回之后,互斥對(duì)象處于什么狀態(tài)?這兩個(gè)問(wèn)題的答案都是“鎖定”。既然已經(jīng)完全理解了 pthread_cond_wait() 調(diào)用,現(xiàn)在來(lái)繼續(xù)研究更簡(jiǎn)單的東西 -- 初始化和真正的發(fā)送信號(hào)和廣播進(jìn)程。到那時(shí),我們將會(huì)對(duì)包含了多線程工作隊(duì)列的 C 代碼了如指掌。




            初始化和清除

            條件變量是一個(gè)需要初始化的真實(shí)數(shù)據(jù)結(jié)構(gòu)。以下就初始化的方法。首先,定義或分配一個(gè)條件變量,如下所示:

            ??? pthread_cond_t mycond;


            然后,調(diào)用以下函數(shù)進(jìn)行初始化:

            ??? pthread_cond_init(&mycond,NULL);


            瞧,初始化完成了!在釋放或廢棄條件變量之前,需要?dú)乃缦滤荆?br />
            ??? pthread_cond_destroy(&mycond);


            很簡(jiǎn)單吧。接著討論 pthread_cond_wait() 調(diào)用。





            等待

            一旦初始化了互斥對(duì)象和條件變量,就可以等待某個(gè)條件,如下所示:

            ??? pthread_cond_wait(&mycond, &mymutex);


            請(qǐng)注意,代碼在邏輯上應(yīng)該包含 mycond 和 mymutex。一個(gè)特定條件只能有一個(gè)互斥對(duì)象,而且條件變量應(yīng)該表示互斥數(shù)據(jù)“內(nèi)部”的一種特殊的條件更改。一個(gè)互斥對(duì)象可以用許多條件變量(例如, cond_empty、cond_full、cond_cleanup),但每個(gè)條件變量只能有一個(gè)互斥對(duì)象。




            發(fā)送信號(hào)和廣播

            對(duì)于發(fā)送信號(hào)和廣播,需要注意一點(diǎn)。如果線程更改某些共享數(shù)據(jù),而且它想要喚醒所有正在等待的線程,則應(yīng)使用 pthread_cond_broadcast 調(diào)用,如下所示:

            ??? pthread_cond_broadcast(&mycond);


            在某些情況下,活動(dòng)線程只需要喚醒第一個(gè)正在睡眠的線程。假設(shè)您只對(duì)隊(duì)列添加了一個(gè)工作作業(yè)。那么只需要喚醒一個(gè)工作程序線程(再喚醒其它線程是不禮貌的!):

            ??? pthread_cond_signal(&mycond);


            此函數(shù)只喚醒一個(gè)線程。如果 POSIX 線程標(biāo)準(zhǔn)允許指定一個(gè)整數(shù),可以讓您喚醒一定數(shù)量的正在睡眠的線程,那就更完美了。但是很可惜,我沒(méi)有被邀請(qǐng)參加會(huì)議。





            工作組

            我將演示如何創(chuàng)建多線程工作組。在這個(gè)方案中,我們創(chuàng)建了許多工作程序線程。每個(gè)線程都會(huì)檢查 wq(“工作隊(duì)列”),查看是否有需要完成的工作。如果有需要完成的工作,那么線程將從隊(duì)列中除去一個(gè)節(jié)點(diǎn),執(zhí)行這些特定工作,然后等待新的工作到達(dá)。

            與此同時(shí),主線程負(fù)責(zé)創(chuàng)建這些工作程序線程、將工作添加到隊(duì)列,然后在它退出時(shí)收集所有工作程序線程。您將會(huì)遇到許多 C 代碼,好好準(zhǔn)備吧!




            隊(duì)列

            需要隊(duì)列是出于兩個(gè)原因。首先,需要隊(duì)列來(lái)保存工作作業(yè)。還需要可用于跟蹤已終止線程的數(shù)據(jù)結(jié)構(gòu)。還記得前幾篇文章(請(qǐng)參閱本文結(jié)尾處的 參考資料)中,我曾提到過(guò)需要使用帶有特定進(jìn)程標(biāo)識(shí)的 pthread_join 嗎?使用“清除隊(duì)列”(稱(chēng)作 "cq")可以解決無(wú)法等待 任何已終止線程的問(wèn)題(稍后將詳細(xì)討論這個(gè)問(wèn)題)。以下是標(biāo)準(zhǔn)隊(duì)列代碼。將此代碼保存到文件 queue.h 和 queue.c:


            queue.h

            /* queue.h
            ** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
            ** Author: Daniel Robbins
            ** Date: 16 Jun 2000
            */
            typedef struct node {
            ? struct node *next;
            } node;
            typedef struct queue {
            ? node *head, *tail;
            } queue;
            void queue_init(queue *myroot);
            void queue_put(queue *myroot, node *mynode);
            node *queue_get(queue *myroot);





            queue.c

            /* queue.c
            ** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
            ** Author: Daniel Robbins
            ** Date: 16 Jun 2000
            **
            ** This set of queue functions was originally thread-aware.? I
            ** redesigned the code to make this set of queue routines
            ** thread-ignorant (just a generic, boring yet very fast set of queue
            ** routines).? Why the change?? Because it makes more sense to have
            ** the thread support as an optional add-on.? Consider a situation
            ** where you want to add 5 nodes to the queue.? With the
            ** thread-enabled version, each call to queue_put() would
            ** automatically lock and unlock the queue mutex 5 times -- that's a
            ** lot of unnecessary overhead.? However, by moving the thread stuff
            ** out of the queue routines, the caller can lock the mutex once at
            ** the beginning, then insert 5 items, and then unlock at the end.
            ** Moving the lock/unlock code out of the queue functions allows for
            ** optimizations that aren't possible otherwise.? It also makes this
            ** code useful for non-threaded applications.
            **
            ** We can easily thread-enable this data structure by using the
            ** data_control type defined in control.c and control.h.? */
            #include <stdio.h>
            #include "queue.h"
            void queue_init(queue *myroot) {
            ? myroot->head=NULL;
            ? myroot->tail=NULL;
            }
            void queue_put(queue *myroot,node *mynode) {
            ? mynode->next=NULL;
            ? if (myroot->tail!=NULL)
            ??? myroot->tail->next=mynode;
            ? myroot->tail=mynode;
            ? if (myroot->:head==NULL)
            ??? myroot->head=mynode;
            }
            node *queue_get(queue *myroot) {
            ? //get from root
            ? node *mynode;
            ? mynode=myroot->head;
            ? if (myroot->head!=NULL)
            ??? myroot->head=myroot->head->next;
            ? return mynode;
            }




            data_control 代碼

            我編寫(xiě)的并不是線程安全的隊(duì)列例程,事實(shí)上我創(chuàng)建了一個(gè)“數(shù)據(jù)包裝”或“控制”結(jié)構(gòu),它可以是任何線程支持的數(shù)據(jù)結(jié)構(gòu)。看一下 control.h:


            control.h

            #include
            typedef struct data_control {
            ? pthread_mutex_t mutex;
            ? pthread_cond_t cond;
            ? int active;
            } data_control;


            現(xiàn)在您看到了 data_control 結(jié)構(gòu)定義,以下是它的視覺(jué)表示:


            所使用的 data_control 結(jié)構(gòu)

            圖像中的鎖代表互斥對(duì)象,它允許對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行互斥訪問(wèn)。黃色的星代表?xiàng)l件變量,它可以睡眠,直到所討論的數(shù)據(jù)結(jié)構(gòu)改變?yōu)橹埂n/off 開(kāi)關(guān)表示整數(shù) "active",它告訴線程此數(shù)據(jù)是否是活動(dòng)的。在代碼中,我使用整數(shù) active 作為標(biāo)志,告訴工作隊(duì)列何時(shí)應(yīng)該關(guān)閉。以下是 control.c:


            control.c

            /* control.c
            ** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
            ** Author: Daniel Robbins
            ** Date: 16 Jun 2000
            **
            ** These routines provide an easy way to make any type of
            ** data-structure thread-aware.? Simply associate a data_control
            ** structure with the data structure (by creating a new struct, for
            ** example).? Then, simply lock and unlock the mutex, or
            ** wait/signal/broadcast on the condition variable in the data_control
            ** structure as needed.
            **
            ** data_control structs contain an int called "active".? This int is
            ** intended to be used for a specific kind of multithreaded design,
            ** where each thread checks the state of "active" every time it locks
            ** the mutex.? If active is 0, the thread knows that instead of doing
            ** its normal routine, it should stop itself.? If active is 1, it
            ** should continue as normal.? So, by setting active to 0, a
            ** controlling thread can easily inform a thread work crew to shut
            ** down instead of processing new jobs.? Use the control_activate()
            ** and control_deactivate() functions, which will also broadcast on
            ** the data_control struct's condition variable, so that all threads
            ** stuck in pthread_cond_wait() will wake up, have an opportunity to
            ** notice the change, and then terminate.
            */
            #include "control.h"
            int control_init(data_control *mycontrol) {
            ? int mystatus;
            ? if (pthread_mutex_init(&(mycontrol->mutex),NULL))
            ??? return 1;
            ? if (pthread_cond_init(&(mycontrol->cond),NULL))
            ??? return 1;
            ? mycontrol->active=0;
            ? return 0;
            }
            int control_destroy(data_control *mycontrol) {
            ? int mystatus;
            ? if (pthread_cond_destroy(&(mycontrol->cond)))
            ??? return 1;
            ? if (pthread_cond_destroy(&(mycontrol->cond)))
            ??? return 1;
            ? mycontrol->active=0;
            ? return 0;
            }
            int control_activate(data_control *mycontrol) {
            ? int mystatus;
            ? if (pthread_mutex_lock(&(mycontrol->mutex)))
            ??? return 0;
            ? mycontrol->active=1;
            ? pthread_mutex_unlock(&(mycontrol->mutex));
            ? pthread_cond_broadcast(&(mycontrol->cond));
            ? return 1;
            }
            int control_deactivate(data_control *mycontrol) {
            ? int mystatus;
            ? if (pthread_mutex_lock(&(mycontrol->mutex)))
            ??? return 0;
            ? mycontrol->active=0;
            ? pthread_mutex_unlock(&(mycontrol->mutex));
            ? pthread_cond_broadcast(&(mycontrol->cond));
            ? return 1;
            }





            調(diào)試時(shí)間

            在開(kāi)始調(diào)試之前,還需要一個(gè)文件。以下是 dbug.h:


            dbug.h

            #define dabort() \
            ?{? printf("Aborting at line %d in source file %s\n",__LINE__,__FILE__); abort(); }


            此代碼用于處理工作組代碼中的不可糾正錯(cuò)誤。




            工作組代碼

            說(shuō)到工作組代碼,以下就是:


            workcrew.c

            #include <stdio.h>
            #include <stdlib.h>
            #include "control.h"
            #include "queue.h"
            #include "dbug.h"
            /* the work_queue holds tasks for the various threads to complete. */
            struct work_queue {
            ? data_control control;
            ? queue work;
            } wq;
            /* I added a job number to the work node.? Normally, the work node
            ?? would contain additional data that needed to be processed. */
            typedef struct work_node {
            ? struct node *next;
            ? int jobnum;
            } wnode;
            /* the cleanup queue holds stopped threads.? Before a thread
            ?? terminates, it adds itself to this list.? Since the main thread is
            ?? waiting for changes in this list, it will then wake up and clean up
            ?? the newly terminated thread. */
            struct cleanup_queue {
            ? data_control control;
            ? queue cleanup;
            } cq;
            /* I added a thread number (for debugging/instructional purposes) and
            ?? a thread id to the cleanup node.? The cleanup node gets passed to
            ?? the new thread on startup, and just before the thread stops, it
            ?? attaches the cleanup node to the cleanup queue.? The main thread
            ?? monitors the cleanup queue and is the one that performs the
            ?? necessary cleanup. */
            typedef struct cleanup_node {
            ? struct node *next;
            ? int threadnum;
            ? pthread_t tid;
            } cnode;
            void *threadfunc(void *myarg) {
            ? wnode *mywork;
            ? cnode *mynode;
            ? mynode=(cnode *) myarg;
            ? pthread_mutex_lock(&wq.control.mutex);
            ? while (wq.control.active) {
            ??? while (wq.work.head==NULL && wq.control.active) {
            ????? pthread_cond_wait(&wq.control.cond, &wq.control.mutex);
            ??? }
            ??? if (!wq.control.active)
            ????? break;
            ??? //we got something!
            ??? mywork=(wnode *) queue_get(&wq.work);
            ??? pthread_mutex_unlock(&wq.control.mutex);
            ??? //perform processing...
            ??? printf("Thread number %d processing job %d\n",mynode->threadnum,mywork->jobnum);
            ??? free(mywork);
            ??? pthread_mutex_lock(&wq.control.mutex);
            ? }
            ? pthread_mutex_unlock(&wq.control.mutex);
            ? pthread_mutex_lock(&cq.control.mutex);
            ? queue_put(&cq.cleanup,(node *) mynode);
            ? pthread_mutex_unlock(&cq.control.mutex);
            ? pthread_cond_signal(&cq.control.cond);
            ? printf("thread %d shutting down...\n",mynode->threadnum);
            ? return NULL;
            ?
            }
            #define NUM_WORKERS 4
            int numthreads;
            void join_threads(void) {
            ? cnode *curnode;
            ? printf("joining threads...\n");
            ? while (numthreads) {
            ??? pthread_mutex_lock(&cq.control.mutex);
            ??? /* below, we sleep until there really is a new cleanup node.? This
            ?????? takes care of any false wakeups... even if we break out of
            ?????? pthread_cond_wait(), we don't make any assumptions that the
            ?????? condition we were waiting for is true.? */
            ??? while (cq.cleanup.head==NULL) {
            ????? pthread_cond_wait(&cq.control.cond,&cq.control.mutex);
            ??? }
            ??? /* at this point, we hold the mutex and there is an item in the
            ?????? list that we need to process.? First, we remove the node from
            ?????? the queue.? Then, we call pthread_join() on the tid stored in
            ?????? the node.? When pthread_join() returns, we have cleaned up
            ?????? after a thread.? Only then do we free() the node, decrement the
            ?????? number of additional threads we need to wait for and repeat the
            ?????? entire process, if necessary */
            ????? curnode = (cnode *) queue_get(&cq.cleanup);
            ????? pthread_mutex_unlock(&cq.control.mutex);
            ????? pthread_join(curnode->tid,NULL);
            ????? printf("joined with thread %d\n",curnode->threadnum);
            ????? free(curnode);
            ????? numthreads--;
            ? }
            }
            int create_threads(void) {
            ? int x;
            ? cnode *curnode;
            ? for (x=0; x<NUM_WORKERS; x++) {
            ??? curnode=malloc(sizeof(cnode));
            ??? if (!curnode)
            ????? return 1;
            ??? curnode->threadnum=x;
            ??? if (pthread_create(&curnode->tid, NULL, threadfunc, (void *) curnode))
            ????? return 1;
            ??? printf("created thread %d\n",x);
            ??? numthreads++;
            ? }
            ? return 0;
            }
            void initialize_structs(void) {
            ? numthreads=0;
            ? if (control_init(&wq.control))
            ??? dabort();
            ? queue_init(&wq.work);
            ? if (control_init(&cq.control)) {
            ??? control_destroy(&wq.control);
            ??? dabort();
            ? }
            ? queue_init(&wq.work);
            ? control_activate(&wq.control);
            }
            void cleanup_structs(void) {
            ? control_destroy(&cq.control);
            ? control_destroy(&wq.control);
            }
            int main(void) {
            ? int x;
            ? wnode *mywork;
            ? initialize_structs();
            ? /* CREATION */
            ?
            ? if (create_threads()) {
            ??? printf("Error starting threads... cleaning up.\n");
            ??? join_threads();
            ??? dabort();
            ? }
            ? pthread_mutex_lock(&wq.control.mutex);
            ? for (x=0; x<16000; x++) {
            ??? mywork=malloc(sizeof(wnode));
            ??? if (!mywork) {
            ????? printf("ouch! can't malloc!\n");
            ????? break;
            ??? }
            ??? mywork->jobnum=x;
            ??? queue_put(&wq.work,(node *) mywork);
            ? }
            ? pthread_mutex_unlock(&wq.control.mutex);
            ? pthread_cond_broadcast(&wq.control.cond);
            ? printf("sleeping...\n");
            ? sleep(2);
            ? printf("deactivating work queue...\n");
            ? control_deactivate(&wq.control);
            ? /* CLEANUP? */
            ? join_threads();
            ? cleanup_structs();
            }







            代碼初排

            現(xiàn)在來(lái)快速初排代碼。定義的第一個(gè)結(jié)構(gòu)稱(chēng)作 "wq",它包含了 data_control 和隊(duì)列頭。data_control 結(jié)構(gòu)用于仲裁對(duì)整個(gè)隊(duì)列的訪問(wèn),包括隊(duì)列中的節(jié)點(diǎn)。下一步工作是定義實(shí)際的工作節(jié)點(diǎn)。要使代碼符合本文中的示例,此處所包含的都是作業(yè)號(hào)。

            接著,創(chuàng)建清除隊(duì)列。注釋說(shuō)明了它的工作方式。好,現(xiàn)在讓我們跳過(guò) threadfunc()、join_threads()、create_threads() 和 initialize_structs() 調(diào)用,直接跳到 main()。所做的第一件事就是初始化結(jié)構(gòu) -- 這包括初始化 data_controls 和隊(duì)列,以及激活工作隊(duì)列。




            有關(guān)清除的注意事項(xiàng)

            現(xiàn)在初始化線程。如果看一下 create_threads() 調(diào)用,似乎一切正常 -- 除了一件事。請(qǐng)注意,我們正在分配清除節(jié)點(diǎn),以及初始化它的線程號(hào)和 TID 組件。我們還將清除節(jié)點(diǎn)作為初始自變量傳遞給每一個(gè)新的工作程序線程。為什么這樣做?

            因?yàn)楫?dāng)某個(gè)工作程序線程退出時(shí),它會(huì)將其清除節(jié)點(diǎn)連接到清除隊(duì)列,然后終止。那時(shí),主線程會(huì)在清除隊(duì)列中檢測(cè)到這個(gè)節(jié)點(diǎn)(利用條件變量),并將這個(gè)節(jié)點(diǎn)移出隊(duì)列。因?yàn)?TID(線程標(biāo)識(shí))存儲(chǔ)在清除節(jié)點(diǎn)中,所以主線程可以確切知道哪個(gè)線程已終止了。然后,主線程將調(diào)用 pthread_join(tid),并聯(lián)接適當(dāng)?shù)墓ぷ鞒绦蚓€程。如果沒(méi)有做記錄,那么主線程就需要按任意順序聯(lián)接工作程序線程,可能是按它們的創(chuàng)建順序。由于線程不一定按此順序終止,那么主線程可能會(huì)在已經(jīng)聯(lián)接了十個(gè)線程時(shí),等待聯(lián)接另一個(gè)線程。您能理解這種設(shè)計(jì)決策是如何使關(guān)閉代碼加速的嗎(尤其在使用幾百個(gè)工作程序線程的情況下)?




            創(chuàng)建工作

            我們已啟動(dòng)了工作程序線程(它們已經(jīng)完成了執(zhí)行 threadfunc(),稍后將討論此函數(shù)),現(xiàn)在主線程開(kāi)始將工作節(jié)點(diǎn)插入工作隊(duì)列。首先,它鎖定 wq 的控制互斥對(duì)象,然后分配 16000 個(gè)工作包,將它們逐個(gè)插入隊(duì)列。完成之后,將調(diào)用 pthread_cond_broadcast(),于是所有正在睡眠的線程會(huì)被喚醒,并開(kāi)始執(zhí)行工作。此時(shí),主線程將睡眠兩秒鐘,然后釋放工作隊(duì)列,并通知工作程序線程終止活動(dòng)。接著,主線程會(huì)調(diào)用 join_threads() 函數(shù)來(lái)清除所有工作程序線程。




            threadfunc()

            現(xiàn)在來(lái)討論 threadfunc(),這是所有工作程序線程都要執(zhí)行的代碼。當(dāng)工作程序線程啟動(dòng)時(shí),它會(huì)立即鎖定工作隊(duì)列互斥對(duì)象,獲取一個(gè)工作節(jié)點(diǎn)(如果有的話),然后對(duì)它進(jìn)行處理。如果沒(méi)有工作,則調(diào)用 pthread_cond_wait()。您會(huì)注意到這個(gè)調(diào)用在一個(gè)非常緊湊的 while() 循環(huán)中,這是非常重要的。當(dāng)從 pthread_cond_wait() 調(diào)用中蘇醒時(shí),決不能認(rèn)為條件肯定發(fā)生了 -- 它 可能發(fā)生了,也可能沒(méi)有發(fā)生。如果發(fā)生了這種情況,即錯(cuò)誤地喚醒了線程,而列表是空的,那么 while 循環(huán)將再次調(diào)用 pthread_cond_wait()。

            如果有一個(gè)工作節(jié)點(diǎn),那么我們只打印它的作業(yè)號(hào),釋放它并退出。然而,實(shí)際代碼會(huì)執(zhí)行一些更實(shí)質(zhì)性的操作。在 while() 循環(huán)結(jié)尾,我們鎖定了互斥對(duì)象,以便檢查 active 變量,以及在循環(huán)頂部檢查新的工作節(jié)點(diǎn)。如果執(zhí)行完此代碼,就會(huì)發(fā)現(xiàn)如果 wq.control.active 是 0,while 循環(huán)就會(huì)終止,并會(huì)執(zhí)行 threadfunc() 結(jié)尾處的清除代碼。

            工作程序線程的清除代碼部件非常有趣。首先,由于 pthread_cond_wait() 返回了鎖定的互斥對(duì)象,它會(huì)對(duì) work_queue 解鎖。然后,它鎖定清除隊(duì)列,添加清除代碼(包含了 TID,主線程將使用此 TID 來(lái)調(diào)用 pthread_join()),然后再對(duì)清除隊(duì)列解鎖。此后,它發(fā)信號(hào)給所有 cq 等待者 (pthread_cond_signal(&cq.control.cond)),于是主線程就知道有一個(gè)待處理的新節(jié)點(diǎn)。我們不使用 pthread_cond_broadcast(),因?yàn)闆](méi)有這個(gè)必要 -- 只有一個(gè)線程(主線程)在等待清除隊(duì)列中的新節(jié)點(diǎn)。當(dāng)它調(diào)用 join_threads() 時(shí),工作程序線程將打印關(guān)閉消息,然后終止,等待主線程發(fā)出的 pthread_join() 調(diào)用。




            join_threads()

            如果要查看關(guān)于如何使用條件變量的簡(jiǎn)單示例,請(qǐng)參考 join_threads() 函數(shù)。如果還有工作程序線程,join_threads() 會(huì)一直執(zhí)行,等待清除隊(duì)列中新的清除節(jié)點(diǎn)。如果有新節(jié)點(diǎn),我們會(huì)將此節(jié)點(diǎn)移出隊(duì)列、對(duì)清除隊(duì)列解鎖(從而使工作程序可以添加清除節(jié)點(diǎn))、聯(lián)接新的工作程序線程(使用存儲(chǔ)在清除節(jié)點(diǎn)中的 TID)、釋放清除節(jié)點(diǎn)、減少“現(xiàn)有”線程的數(shù)量,然后繼續(xù)。





            結(jié)束語(yǔ)

            現(xiàn)在已經(jīng)到了“POSIX 線程詳解”系列的尾聲,希望您已經(jīng)準(zhǔn)備好開(kāi)始將多線程代碼添加到您自己的應(yīng)用程序中。有關(guān)詳細(xì)信息,請(qǐng)參閱 參考資料部分,這部分內(nèi)容還包含了本文中使用的所有源碼的 tar 文件。下一個(gè)系列中再見(jiàn)!






            參考資料

            ??? * 您可以參閱本文在 developerWorks 全球站點(diǎn)上的 英文原文.

            ??? * 參閱 Linux threads中的文檔,Sean Walton, KB7rfa

            ??? * 在 An Introduction to Pthreads-Tcl 中,查看對(duì) Tcl 的更改以使其能夠使用 POSIX 線程

            ??? * 使用友好的 Linux pthread 在線幫助 ("man -k pthread")

            ??? * 參考 POSIX and DCE threads for Linux主頁(yè)

            ??? * 查看 The LinuxThreads Library

            ??? * Proolix ,一種簡(jiǎn)單遵從 POSIX 標(biāo)準(zhǔn)的操作系統(tǒng),用于 i8086+,一直在開(kāi)發(fā)中

            ??? * 閱讀 David R. Butenhof 的著作 Programming with POSIX Threads,書(shū)中討論了許多問(wèn)題,其中談到不使用互斥對(duì)象是可能出現(xiàn)的種種情況

            ??? * 查閱 W. Richard Stevens 的著作 UNIX Network Programming: Network APIs: Sockets and XTI, Volume 1



            關(guān)于作者

            ?? ??? ?

            Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的總裁兼 CEO, Gentoo 項(xiàng)目的總設(shè)計(jì)師,多本 MacMillan 出版書(shū)籍的作者,包括: Caldera OpenLinux Unleashed、 SuSE Linux Unleashed和 Samba Unleashed 。Daniel 自小學(xué)二年級(jí)起就與計(jì)算機(jī)結(jié)下不解之緣,那時(shí)他首先接觸的是 Logo 程序語(yǔ)言,并沉溺于 Pac-Man 游戲中。這也許就是他至今仍擔(dān)任 SONY Electronic Publishing/Psygnosis 的首席圖形設(shè)計(jì)師的原因所在。Daniel 喜歡與妻子 Mary 和剛出生的女兒 Hadassah 一起共渡時(shí)光。可通過(guò) drobbins@gentoo.org 與 Daniel Robbins 取得聯(lián)系。

            posted on 2007-03-16 12:08 Khan 閱讀(1843) 評(píng)論(0)  編輯 收藏 引用 所屬分類(lèi): GCC/G++跨平臺(tái)開(kāi)發(fā)

            热久久国产欧美一区二区精品| 久久精品国产亚洲精品2020 | 色婷婷久久久SWAG精品| 久久国产AVJUST麻豆| 久久免费高清视频| 久久强奷乱码老熟女网站| 久久AV高清无码| 国产精品亚洲综合久久 | 欧美麻豆久久久久久中文| 777午夜精品久久av蜜臀| 国产成人精品综合久久久| 久久人人爽人人人人片av| 久久久久亚洲精品男人的天堂| 97视频久久久| 青青草国产97免久久费观看| 99久久中文字幕| 久久精品国产免费观看| 老司机午夜网站国内精品久久久久久久久 | 91久久精品国产成人久久| 色欲av伊人久久大香线蕉影院| 国产福利电影一区二区三区久久久久成人精品综合 | 久久精品综合网| 久久人人爽人人爽人人片AV麻豆 | 色狠狠久久AV五月综合| 久久久久99这里有精品10| 久久精品国产精品亚洲下载| 久久福利青草精品资源站| 国内精品久久久久影院一蜜桃| 久久精品国产欧美日韩99热| 久久人妻少妇嫩草AV无码蜜桃| 大蕉久久伊人中文字幕| 18岁日韩内射颜射午夜久久成人 | 伊人情人综合成人久久网小说| 久久久不卡国产精品一区二区 | 国产成人久久精品一区二区三区| 亚洲AV伊人久久青青草原| 欧美成人免费观看久久| 亚洲精品视频久久久| 一本色道久久综合| 久久久久亚洲精品日久生情| 国产精品久久新婚兰兰|