• <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>
            posts - 15, comments - 9, trackbacks - 0, articles - 0

            [原文地址]http://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/


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

            線程是有趣的

            了解如何正確運(yùn)用線程是每一個(gè)優(yōu)秀程序員必備的素質(zhì)。線程類似于進(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() 編寫過(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)單的答案。雖然有許多不同種類的本地 IPC (進(jìn)程間通信),但它們都遇到兩個(gè)重要障礙:

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

            雙重壞事: 開(kāi)銷和復(fù)雜性都非好事。如果曾經(jīng)為了支持 IPC 而對(duì)程序大動(dòng)干戈過(guò),那么您就會(huì)真正欣賞線程提供的簡(jiǎn)單共享內(nèi)存機(jī)制。由于所有的線程都駐留在同一內(nèi)存空間,POSIX 線程無(wú)需進(jìn)行開(kāi)銷大而復(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)銷很小。內(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)行)。特定類型線程程序(尤其是 CPU 密集型程序)的性能將隨系統(tǒng)中處理器的數(shù)目幾乎線性地提高。如果正在編寫 CPU 非常密集型的程序,則絕對(duì)想設(shè)法在代碼中使用多線程。一旦掌握了線程編碼,無(wú)需使用繁瑣的 IPC 和其它復(fù)雜的通信機(jī)制,就能夠以全新和創(chuàng)造性的方法解決編碼難題。所有這些特性配合在一起使得多線程編程更有趣、快速和靈活。


            線程是可移植的

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

            “__clone 調(diào)用是特定于 Linux 平臺(tái)的,不適用于實(shí)現(xiàn)可移植的程序。欲編寫線程化應(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 線程。如果想編寫 可移植的 多線程代碼,代碼可運(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,類型是 pthread_t。pthread_t 類型在 pthread.h 中定義,通常稱為“線程 id”(縮寫為 "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í)返回值的類型也是 void *。這表明可以用 void * 向新線程傳遞任意類型的數(shù)據(jù),新線程完成時(shí)也可返回任意類型的數(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è)線程。可以這樣理解:如果編寫的程序根本沒(méi)有使用 POSIX 線程,則該程序是單線程的(這個(gè)單線程稱為“主”線程)。創(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è)線程稱為線程 1,線程 1 創(chuàng)建了稱為線程 2 的線程,則線程 1 自己沒(méi)有必要調(diào)用 pthread_join() 來(lái)合并線程 2,程序中其它任一線程都可以做到。當(dāng)編寫大量使用線程的代碼時(shí),這就可能允許發(fā)生有趣的事情。例如,可以創(chuàng)建一個(gè)包含所有已停止線程的全局“死線程列表”,然后讓一個(gè)專門的清理線程專等停止的線程加到列表中。這個(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 的值寫回 myglobal 時(shí),就覆蓋了主線程所做的修改。

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

            由于是將 myglobal 復(fù)制給 j 并且等了一秒之后才寫回時(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)。


            參考資料



            關(guān)于作者

             

            Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的總裁兼 CEO, Gentoo 項(xiàng)目的總設(shè)計(jì)師,多本 MacMillan 出版書(shū)籍的作者,包括: Caldera OpenLinux UnleashedSuSE Linux UnleashedSamba 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)系。

            久久久国产精华液| 久久99久久99小草精品免视看| 热久久视久久精品18| 99久久精品国产毛片| 久久久精品2019免费观看| 国内精品人妻无码久久久影院导航| 久久久久成人精品无码| 麻豆亚洲AV永久无码精品久久| 一本久久免费视频| 国产香蕉久久精品综合网| 亚洲欧美国产日韩综合久久| 久久精品国产亚洲av麻豆色欲| 91精品日韩人妻无码久久不卡| 中文字幕久久精品无码| 久久精品国产亚洲精品| 国产亚洲欧美成人久久片| 国产精品成人久久久| 久久久久女教师免费一区| 国产精品久久午夜夜伦鲁鲁| 久久精品国产亚洲AV香蕉| 一本色道久久综合| 久久久久亚洲爆乳少妇无| 久久天天躁夜夜躁狠狠躁2022| 色天使久久综合网天天| 久久精品成人欧美大片| 99久久99久久精品国产| 久久精品国产亚洲沈樵| 97超级碰碰碰久久久久| 国产精品青草久久久久福利99| 久久国产成人精品国产成人亚洲| 亚洲国产成人精品久久久国产成人一区二区三区综 | 国产伊人久久| www亚洲欲色成人久久精品| 秋霞久久国产精品电影院| 久久影视综合亚洲| 久久人妻少妇嫩草AV蜜桃| 久久无码一区二区三区少妇| 久久99精品九九九久久婷婷| 久久99精品久久久久久噜噜| 久久精品中文字幕有码| 日韩电影久久久被窝网|