<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    一江春水向東流

    做一個(gè)有思想的人,期待與每一位熱愛思考的人交流,您的關(guān)注是對(duì)我最大的支持。

      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      44 隨筆 :: 139 文章 :: 81 評(píng)論 :: 0 Trackbacks
    一篇介紹Linux下的多線程編程的文章
    1 引言
      線程(thread)技術(shù)早在60年代就被提出,但真正應(yīng)用多線程到操作系統(tǒng)中去,是在80年代中期,solaris是這方面的佼佼者。傳統(tǒng)的
    Unix也支持線程的概念,但是在一個(gè)進(jìn)程(process)中只允許有一個(gè)線程,這樣多線程就意味著多進(jìn)程。現(xiàn)在,多線程技術(shù)已經(jīng)被許多操作系
    統(tǒng)所支持,包括Windows/NT,當(dāng)然,也包括Linux。
      為什么有了進(jìn)程的概念后,還要再引入線程呢?使用多線程到底有哪些好處?什么的系統(tǒng)應(yīng)該選用多線程?我們首先必須回答這些問題。
      使用多線程的理由之一是和進(jìn)程相比,它是一種非常"節(jié)儉"的多任務(wù)操作方式。我們知道,在Linux系統(tǒng)下,啟動(dòng)一個(gè)新的進(jìn)程必須分配給
    它獨(dú)立的地址空間,建立眾多的數(shù)據(jù)表來維護(hù)它的代碼段、堆棧段和數(shù)據(jù)段,這是一種"昂貴"的多任務(wù)工作方式。而運(yùn)行于一個(gè)進(jìn)程中的多個(gè)
    線程,它們彼此之間使用相同的地址空間,共享大部分?jǐn)?shù)據(jù),啟動(dòng)一個(gè)線程所花費(fèi)的空間遠(yuǎn)遠(yuǎn)小于啟動(dòng)一個(gè)進(jìn)程所花費(fèi)的空間,而且,線程間
    彼此切換所需的時(shí)間也遠(yuǎn)遠(yuǎn)小于進(jìn)程間切換所需要的時(shí)間。據(jù)統(tǒng)計(jì),總的說來,一個(gè)進(jìn)程的開銷大約是一個(gè)線程開銷的30倍左右,當(dāng)然,在具
    體的系統(tǒng)上,這個(gè)數(shù)據(jù)可能會(huì)有較大的區(qū)別。
      使用多線程的理由之二是線程間方便的通信機(jī)制。對(duì)不同進(jìn)程來說,它們具有獨(dú)立的數(shù)據(jù)空間,要進(jìn)行數(shù)據(jù)的傳遞只能通過通信的方式進(jìn)
    行,這種方式不僅費(fèi)時(shí),而且很不方便。線程則不然,由于同一進(jìn)程下的線程之間共享數(shù)據(jù)空間,所以一個(gè)線程的數(shù)據(jù)可以直接為其它線程所
    用,這不僅快捷,而且方便。當(dāng)然,數(shù)據(jù)的共享也帶來其他一些問題,有的變量不能同時(shí)被兩個(gè)線程所修改,有的子程序中聲明為static的數(shù)
    據(jù)更有可能給多線程程序帶來災(zāi)難性的打擊,這些正是編寫多線程程序時(shí)最需要注意的地方。
      除了以上所說的優(yōu)點(diǎn)外,不和進(jìn)程比較,多線程程序作為一種多任務(wù)、并發(fā)的工作方式,當(dāng)然有以下的優(yōu)點(diǎn):
      1)
    提高應(yīng)用程序響應(yīng)。這對(duì)圖形界面的程序尤其有意義,當(dāng)一個(gè)操作耗時(shí)很長(zhǎng)時(shí),整個(gè)系統(tǒng)都會(huì)等待這個(gè)操作,此時(shí)程序不會(huì)響應(yīng)鍵盤、鼠標(biāo)、
    菜單的操作,而使用多線程技術(shù),將耗時(shí)長(zhǎng)的操作(time consuming)置于一個(gè)新的線程,可以避免這種尷尬的情況。
      2) 使多CPU系統(tǒng)更加有效。操作系統(tǒng)會(huì)保證當(dāng)線程數(shù)不大于CPU數(shù)目時(shí),不同的線程運(yùn)行于不同的CPU上。
      3) 改善程序結(jié)構(gòu)。一個(gè)既長(zhǎng)又復(fù)雜的進(jìn)程可以考慮分為多個(gè)線程,成為幾個(gè)獨(dú)立或半獨(dú)立的運(yùn)行部分,這樣的程序會(huì)利于理解和修改。
      下面我們先來嘗試編寫一個(gè)簡(jiǎn)單的多線程程序。
    2 簡(jiǎn)單的多線程編程
      Linux系統(tǒng)下的多線程遵循POSIX線程接口,稱為pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時(shí)需要使用庫(kù)libp
    thread.a。順便說一下,Linux下pthread的實(shí)現(xiàn)是通過系統(tǒng)調(diào)用clone()來實(shí)現(xiàn)的。clone()是Linux所特有的系統(tǒng)調(diào)用,它的使用方式類似
    fork,關(guān)于clone()的詳細(xì)情況,有興趣的讀者可以去查看有關(guān)文檔說明。下面我們展示一個(gè)最簡(jiǎn)單的多線程程序 example1.c。
    /* example.c*/
    #include <stdio.h>
    #include <pthread.h>
    void thread(void)
    {
    int i;
    for(i=0;i<3;i++)
    printf("This is a pthread.n");
    }
    int main(void)
    {
    pthread_t id;
    int i,ret;
    ret=pthread_create(&id,NULL,(void *) thread,NULL);
    if(ret!=0){
    printf ("Create pthread error!n");
    exit (1);
    }
    for(i=0;i<3;i++)
    printf("This is the main process.n");
    pthread_join(id,NULL);
    return (0);
    }
    我們編譯此程序:
    gcc example1.c -lpthread -o example1
    運(yùn)行example1,我們得到如下結(jié)果:
    This is the main process.
    This is a pthread.
    This is the main process.
    This is the main process.
    This is a pthread.
    This is a pthread.
    再次運(yùn)行,我們可能得到如下結(jié)果:
    This is a pthread.
    This is the main process.
    This is a pthread.
    This is the main process.
    This is a pthread.
    This is the main process.
      前后兩次結(jié)果不一樣,這是兩個(gè)線程爭(zhēng)奪CPU資源的結(jié)果。上面的示例中,我們使用到了兩個(gè)函數(shù),  pthread_create和pthread_join,
    并聲明了一個(gè)pthread_t型的變量。
      pthread_t在頭文件/usr/include/bits/pthreadtypes.h中定義:
      typedef unsigned long int pthread_t;
      它是一個(gè)線程的標(biāo)識(shí)符。函數(shù)pthread_create用來創(chuàng)建一個(gè)線程,它的原型為:
      extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
      void *(*__start_routine) (void *), void *__arg));
      第一個(gè)參數(shù)為指向線程標(biāo)識(shí)符的指針,第二個(gè)參數(shù)用來設(shè)置線程屬性,第三個(gè)參數(shù)是線程運(yùn)行函數(shù)的起始地址,最后一個(gè)參數(shù)是運(yùn)行函數(shù)
    的參數(shù)。這里,我們的函數(shù)thread不需要參數(shù),所以最后一個(gè)參數(shù)設(shè)為空指針。第二個(gè)參數(shù)我們也設(shè)為空指針,這樣將生成默認(rèn)屬性的線程。
    對(duì)線程屬性的設(shè)定和修改我們將在下一節(jié)闡述。當(dāng)創(chuàng)建線程成功時(shí),函數(shù)返回0,若不為0則說明創(chuàng)建線程失敗,常見的錯(cuò)誤返回代碼為EAGAIN
    和EINVAL。前者表示系統(tǒng)限制創(chuàng)建新的線程,例如線程數(shù)目過多了;后者表示第二個(gè)參數(shù)代表的線程屬性值非法。創(chuàng)建線程成功后,新創(chuàng)建的
    線程則運(yùn)行參數(shù)三和參數(shù)四確定的函數(shù),原來的線程則繼續(xù)運(yùn)行下一行代碼。
      函數(shù)pthread_join用來等待一個(gè)線程的結(jié)束。函數(shù)原型為:
      extern int pthread_join __P ((pthread_t __th, void **__thread_return));
      第一個(gè)參數(shù)為被等待的線程標(biāo)識(shí)符,第二個(gè)參數(shù)為一個(gè)用戶定義的指針,它可以用來存儲(chǔ)被等待線程的返回值。這個(gè)函數(shù)是一個(gè)線程阻塞
    的函數(shù),調(diào)用它的函數(shù)將一直等待到被等待的線程結(jié)束為止,當(dāng)函數(shù)返回時(shí),被等待線程的資源被收回。一個(gè)線程的結(jié)束有兩種途徑,一種是
    象我們上面的例子一樣,函數(shù)結(jié)束了,調(diào)用它的線程也就結(jié)束了;另一種方式是通過函數(shù)pthread_exit來實(shí)現(xiàn)。它的函數(shù)原型為:
      extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
      唯一的參數(shù)是函數(shù)的返回代碼,只要pthread_join中的第二個(gè)參數(shù)thread_return不是NULL,這個(gè)值將被傳遞給
    thread_return。最后要說明的是,一個(gè)線程不能被多個(gè)線程等待,否則第一個(gè)接收到信號(hào)的線程成功返回,其余調(diào)用pthread_join的線程則返
    回錯(cuò)誤代碼ESRCH。
      在這一節(jié)里,我們編寫了一個(gè)最簡(jiǎn)單的線程,并掌握了最常用的三個(gè)函數(shù)pthread_create,pthread_join和pthread_exit。下面,我們來
    了解線程的一些常用屬性以及如何設(shè)置這些屬性。
    3 修改線程的屬性
      在上一節(jié)的例子里,我們用pthread_create函數(shù)創(chuàng)建了一個(gè)線程,在這個(gè)線程中,我們使用了默認(rèn)參數(shù),即將該函數(shù)的第二個(gè)參數(shù)設(shè)為NUL
    L。的確,對(duì)大多數(shù)程序來說,使用默認(rèn)屬性就夠了,但我們還是有必要來了解一下線程的有關(guān)屬性。
      屬性結(jié)構(gòu)為pthread_attr_t,它同樣在頭文件/usr/include/pthread.h中定義,喜歡追根問底的人可以自己去查看。屬性值不能直接設(shè)置
    ,須使用相關(guān)函數(shù)進(jìn)行操作,初始化的函數(shù)為pthread_attr_init,這個(gè)函數(shù)必須在pthread_create函數(shù)之前調(diào)用。屬性對(duì)象主要包括是否綁定
    、是否分離、堆棧地址、堆棧大小、優(yōu)先級(jí)。默認(rèn)的屬性為非綁定、非分離、缺省1M的堆棧、與父進(jìn)程同樣級(jí)別的優(yōu)先級(jí)。
      關(guān)于線程的綁定,牽涉到另外一個(gè)概念:輕進(jìn)程(LWP:Light Weight
    Process)。輕進(jìn)程可以理解為內(nèi)核線程,它位于用戶層和系統(tǒng)層之間。系統(tǒng)對(duì)線程資源的分配、對(duì)線程的控制是通過輕進(jìn)程來實(shí)現(xiàn)的,一個(gè)輕
    進(jìn)程可以控制一個(gè)或多個(gè)線程。默認(rèn)狀況下,啟動(dòng)多少輕進(jìn)程、哪些輕進(jìn)程來控制哪些線程是由系統(tǒng)來控制的,這種狀況即稱為非綁定的。綁
    定狀況下,則顧名思義,即某個(gè)線程固定的"綁"在一個(gè)輕進(jìn)程之上。被綁定的線程具有較高的響應(yīng)速度,這是因?yàn)镃PU時(shí)間片的調(diào)度是面向輕進(jìn)
    程的,綁定的線程可以保證在需要的時(shí)候它總有一個(gè)輕進(jìn)程可用。通過設(shè)置被綁定的輕進(jìn)程的優(yōu)先級(jí)和調(diào)度級(jí)可以使得綁定的線程滿足諸如實(shí)
    時(shí)反應(yīng)之類的要求。
      設(shè)置線程綁定狀態(tài)的函數(shù)為pthread_attr_setscope,它有兩個(gè)參數(shù),第一個(gè)是指向?qū)傩越Y(jié)構(gòu)的指針,第二個(gè)是綁定類型,它有兩個(gè)取值:
    PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)。下面的代碼即創(chuàng)建了一個(gè)綁定的線程。
    #include <pthread.h>
    pthread_attr_t attr;
    pthread_t tid;
    /*初始化屬性值,均設(shè)為默認(rèn)值*/
    pthread_attr_init(&attr);
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    pthread_create(&tid, &attr, (void *) my_function, NULL);
      線程的分離狀態(tài)決定一個(gè)線程以什么樣的方式來終止自己。在上面的例子中,我們采用了線程的默認(rèn)屬性,即為非分離狀態(tài),這種情況下
    ,原有的線程等待創(chuàng)建的線程結(jié)束。只有當(dāng)pthread_join()函數(shù)返回時(shí),創(chuàng)建的線程才算終止,才能釋放自己占用的系統(tǒng)資源。而分離線程
    不是這樣子的,它沒有被其他的線程所等待,自己運(yùn)行結(jié)束了,線程也就終止了,馬上釋放系統(tǒng)資源。程序員應(yīng)該根據(jù)自己的需要,選擇適當(dāng)
    的分離狀態(tài)。設(shè)置線程分離狀態(tài)的函數(shù)為 pthread_attr_setdetachstate(pthread_attr_t *attr, int
    detachstate)。第二個(gè)參數(shù)可選為PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD
    _CREATE_JOINABLE(非分離線程)。這里要注意的一點(diǎn)是,如果設(shè)置一個(gè)線程為分離線程,而這個(gè)線程運(yùn)行又非常快,它很可能在
    pthread_create函數(shù)返回之前就終止了,它終止以后就可能將線程號(hào)和系統(tǒng)資源移交給其他的線程使用,這樣調(diào)用pthread_create的線程就得
    到了錯(cuò)誤的線程號(hào)。要避免這種情況可以采取一定的同步措施,最簡(jiǎn)單的方法之一是可以在被創(chuàng)建的線程里調(diào)用
    pthread_cond_timewait函數(shù),讓這個(gè)線程等待一會(huì)兒,留出足夠的時(shí)間讓函數(shù)pthread_create返回。設(shè)置一段等待時(shí)間,是在多線程編程里常
    用的方法。但是注意不要使用諸如wait()之類的函數(shù),它們是使整個(gè)進(jìn)程睡眠,并不能解決線程同步的問題。
      另外一個(gè)可能常用的屬性是線程的優(yōu)先級(jí),它存放在結(jié)構(gòu)sched_param中。用函數(shù)pthread_attr_getschedparam和函數(shù)
    pthread_attr_setschedparam進(jìn)行存放,一般說來,我們總是先取優(yōu)先級(jí),對(duì)取得的值修改后再存放回去。下面即是一段簡(jiǎn)單的例子。
    #include <stdio.h>
    #include <pthread.h>
    pthread_attr_t attr;
    pthread_t tid;
    sched_param param;
    int newprio=20;
    pthread_attr_init(&attr);
    pthread_attr_getschedparam(&attr, &para;m);
    param.sched_priority=newprio;
    pthread_attr_setschedparam(&attr, &para;m);
    pthread_create(&tid, &attr, (void *)myfunction, myarg);
      
    4 線程的數(shù)據(jù)處理
      和進(jìn)程相比,線程的最大優(yōu)點(diǎn)之一是數(shù)據(jù)的共享性,各個(gè)進(jìn)程共享父進(jìn)程處沿襲的數(shù)據(jù)段,可以方便的獲得、修改數(shù)據(jù)。但這也給多線程
    編程帶來了許多問題。我們必須當(dāng)心有多個(gè)不同的進(jìn)程訪問相同的變量。許多函數(shù)是不可重入的,即同時(shí)不能運(yùn)行一個(gè)函數(shù)的多個(gè)拷貝(除非
    使用不同的數(shù)據(jù)段)。在函數(shù)中聲明的靜態(tài)變量常常帶來問題,函數(shù)的返回值也會(huì)有問題。因?yàn)槿绻祷氐氖呛瘮?shù)內(nèi)部靜態(tài)聲明的空間的地址
    ,則在一個(gè)線程調(diào)用該函數(shù)得到地址后使用該地址指向的數(shù)據(jù)時(shí),別的線程可能調(diào)用此函數(shù)并修改了這一段數(shù)據(jù)。在進(jìn)程中共享的變量必須用
    關(guān)鍵字volatile來定義,這是為了防止編譯器在優(yōu)化時(shí)(如gcc中使用-OX參數(shù))改變它們的使用方式。為了保護(hù)變量,我們必須使用信號(hào)量、
    互斥等方法來保證我們對(duì)變量的正確使用。下面,我們就逐步介紹處理線程數(shù)據(jù)時(shí)的有關(guān)知識(shí)。
    4.1 線程數(shù)據(jù)
      在單線程的程序里,有兩種基本的數(shù)據(jù):全局變量和局部變量。但在多線程程序里,還有第三種數(shù)據(jù)類型:線程數(shù)據(jù)(TSD:
    Thread-Specific
    Data)。它和全局變量很象,在線程內(nèi)部,各個(gè)函數(shù)可以象使用全局變量一樣調(diào)用它,但它對(duì)線程外部的其它線程是不可見的。這種數(shù)據(jù)的必
    要性是顯而易見的。例如我們常見的變量errno,它返回標(biāo)準(zhǔn)的出錯(cuò)信息。它顯然不能是一個(gè)局部變量,幾乎每個(gè)函數(shù)都應(yīng)該可以調(diào)用它;但它
    又不能是一個(gè)全局變量,否則在
    A線程里輸出的很可能是B線程的出錯(cuò)信息。要實(shí)現(xiàn)諸如此類的變量,我們就必須使用線程數(shù)據(jù)。我們?yōu)槊總€(gè)線程數(shù)據(jù)創(chuàng)建一個(gè)鍵,它和這個(gè)鍵
    相關(guān)聯(lián),在各個(gè)線程里,都使用這個(gè)鍵來指代線程數(shù)據(jù),但在不同的線程里,這個(gè)鍵代表的數(shù)據(jù)是不同的,在同一個(gè)線程里,它代表同樣的數(shù)
    據(jù)內(nèi)容。
      和線程數(shù)據(jù)相關(guān)的函數(shù)主要有4個(gè):創(chuàng)建一個(gè)鍵;為一個(gè)鍵指定線程數(shù)據(jù);從一個(gè)鍵讀取線程數(shù)據(jù);刪除鍵。
      創(chuàng)建鍵的函數(shù)原型為:
      extern int pthread_key_create __P ((pthread_key_t *__key,
      void (*__destr_function) (void *)));
      第一個(gè)參數(shù)為指向一個(gè)鍵值的指針,第二個(gè)參數(shù)指明了一個(gè)destructor函數(shù),如果這個(gè)參數(shù)不為空,那么當(dāng)每個(gè)線程結(jié)束時(shí),系統(tǒng)將調(diào)用
    這個(gè)函數(shù)來釋放綁定在這個(gè)鍵上的內(nèi)存塊。這個(gè)函數(shù)常和函數(shù)pthread_once ((pthread_once_t*once_control, void (*initroutine)
    (void)))一起使用,為了讓這個(gè)鍵只被創(chuàng)建一次。函數(shù)pthread_once聲明一個(gè)初始化函數(shù),第一次調(diào)用pthread_once時(shí)它執(zhí)行這個(gè)函數(shù),以后
    的調(diào)用將被它忽略。
      在下面的例子中,我們創(chuàng)建一個(gè)鍵,并將它和某個(gè)數(shù)據(jù)相關(guān)聯(lián)。我們要定義一個(gè)函數(shù)createWindow,這個(gè)函數(shù)定義一個(gè)圖形窗口(數(shù)據(jù)類
    型為Fl_Window *,這是圖形界面開發(fā)工具FLTK中的數(shù)據(jù)類型)。由于各個(gè)線程都會(huì)調(diào)用這個(gè)函數(shù),所以我們使用線程數(shù)據(jù)。
    /* 聲明一個(gè)鍵*/
    pthread_key_t myWinKey;
    /* 函數(shù) createWindow */
    void createWindow ( void ) {
    Fl_Window * win;
    static pthread_once_t once= PTHREAD_ONCE_INIT;
    /* 調(diào)用函數(shù)createMyKey,創(chuàng)建鍵*/
    pthread_once ( & once, createMyKey) ;
    /*win指向一個(gè)新建立的窗口*/
    win=new Fl_Window( 0, 0, 100, 100, "MyWindow");
    /* 對(duì)此窗口作一些可能的設(shè)置工作,如大小、位置、名稱等*/
    setWindow(win);
    /* 將窗口指針值綁定在鍵myWinKey上*/
    pthread_setpecific ( myWinKey, win);
    }
    /* 函數(shù) createMyKey,創(chuàng)建一個(gè)鍵,并指定了destructor */
    void createMyKey ( void ) {
    pthread_keycreate(&myWinKey, freeWinKey);
    }
    /* 函數(shù) freeWinKey,釋放空間*/
    void freeWinKey ( Fl_Window * win){
    delete win;
    }
      這樣,在不同的線程中調(diào)用函數(shù)createMyWin,都可以得到在線程內(nèi)部均可見的窗口變量,這個(gè)變量通過函數(shù)
    pthread_getspecific得到。在上面的例子中,我們已經(jīng)使用了函數(shù)pthread_setspecific來將線程數(shù)據(jù)和一個(gè)鍵綁定在一起。這兩個(gè)函數(shù)的原
    型如下:
      extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
      extern void *pthread_getspecific __P ((pthread_key_t __key));
      這兩個(gè)函數(shù)的參數(shù)意義和使用方法是顯而易見的。要注意的是,用pthread_setspecific為一個(gè)鍵指定新的線程數(shù)據(jù)時(shí),必須自己釋放原有
    的線程數(shù)據(jù)以回收空間。這個(gè)過程函數(shù)pthread_key_delete用來刪除一個(gè)鍵,這個(gè)鍵占用的內(nèi)存將被釋放,但同樣要注意的是,它只釋放鍵占
    用的內(nèi)存,并不釋放該鍵關(guān)聯(lián)的線程數(shù)據(jù)所占用的內(nèi)存資源,而且它也不會(huì)觸發(fā)函數(shù)pthread_key_create中定義的destructor函數(shù)。線程數(shù)據(jù)
    的釋放必須在釋放鍵之前完成。
    4.2 互斥鎖
      互斥鎖用來保證一段時(shí)間內(nèi)只有一個(gè)線程在執(zhí)行一段代碼。必要性顯而易見:假設(shè)各個(gè)線程向同一個(gè)文件順序?qū)懭霐?shù)據(jù),最后得到的結(jié)果
    一定是災(zāi)難性的。
      我們先看下面一段代碼。這是一個(gè)讀/寫程序,它們公用一個(gè)緩沖區(qū),并且我們假定一個(gè)緩沖區(qū)只能保存一條信息。即緩沖區(qū)只有兩個(gè)狀態(tài)
    :有信息或沒有信息。
    void reader_function ( void );
    void writer_function ( void );
    char buffer;
    int buffer_has_item=0;
    pthread_mutex_t mutex;
    struct timespec delay;
    void main ( void ){
    pthread_t reader;
    /* 定義延遲時(shí)間*/
    delay.tv_sec = 2;
    delay.tv_nec = 0;
    /* 用默認(rèn)屬性初始化一個(gè)互斥鎖對(duì)象*/
    pthread_mutex_init (&mutex,NULL);
    pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
    writer_function( );
    }
    void writer_function (void){
    while(1){
    /* 鎖定互斥鎖*/
    pthread_mutex_lock (&mutex);
    if (buffer_has_item==0){
    buffer=make_new_item( );
    buffer_has_item=1;
    }
    /* 打開互斥鎖*/
    pthread_mutex_unlock(&mutex);
    pthread_delay_np(&delay);
    }
    }
    void reader_function(void){
    while(1){
    pthread_mutex_lock(&mutex);
    if(buffer_has_item==1){
    consume_item(buffer);
    buffer_has_item=0;
    }
    pthread_mutex_unlock(&mutex);
    pthread_delay_np(&delay);
    }
    }
      這里聲明了互斥鎖變量mutex,結(jié)構(gòu)pthread_mutex_t為不公開的數(shù)據(jù)類型,其中包含一個(gè)系統(tǒng)分配的屬性對(duì)象。函數(shù)
    pthread_mutex_init用來生成一個(gè)互斥鎖。NULL參數(shù)表明使用默認(rèn)屬性。如果需要聲明特定屬性的互斥鎖,須調(diào)用函數(shù)
    pthread_mutexattr_init。函數(shù)pthread_mutexattr_setpshared和函數(shù)
    pthread_mutexattr_settype用來設(shè)置互斥鎖屬性。前一個(gè)函數(shù)設(shè)置屬性pshared,它有兩個(gè)取值,
    PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同進(jìn)程中的線程同步,后者用于同步本進(jìn)程的不同線程。在上面的例子中,
    我們使用的是默認(rèn)屬性PTHREAD_PROCESS_
    PRIVATE。后者用來設(shè)置互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、
    PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上所、解鎖機(jī)制,一般情況下,選用最后一個(gè)默認(rèn)屬性。
      pthread_mutex_lock聲明開始用互斥鎖上鎖,此后的代碼直至調(diào)用pthread_mutex_unlock為止,均被上鎖,即同一時(shí)間只能被一個(gè)線程調(diào)
    用執(zhí)行。當(dāng)一個(gè)線程執(zhí)行到pthread_mutex_lock處時(shí),如果該鎖此時(shí)被另一個(gè)線程使用,那此線程被阻塞,即程序?qū)⒌却搅硪粋€(gè)線程釋放此
    互斥鎖。在上面的例子中,我們使用了pthread_delay_np函數(shù),讓線程睡眠一段時(shí)間,就是為了防止一個(gè)線程始終占據(jù)此函數(shù)。
      上面的例子非常簡(jiǎn)單,就不再介紹了,需要提出的是在使用互斥鎖的過程中很有可能會(huì)出現(xiàn)死鎖:兩個(gè)線程試圖同時(shí)占用兩個(gè)資源,并按
    不同的次序鎖定相應(yīng)的互斥鎖,例如兩個(gè)線程都需要鎖定互斥鎖1和互斥鎖2,a線程先鎖定互斥鎖1,b線程先鎖定互斥鎖2,這時(shí)就出現(xiàn)了死鎖
    。此時(shí)我們可以使用函數(shù)
    pthread_mutex_trylock,它是函數(shù)pthread_mutex_lock的非阻塞版本,當(dāng)它發(fā)現(xiàn)死鎖不可避免時(shí),它會(huì)返回相應(yīng)的信息,程序員可以針對(duì)死鎖
    做出相應(yīng)的處理。另外不同的互斥鎖類型對(duì)死鎖的處理不一樣,但最主要的還是要程序員自己在程序設(shè)計(jì)注意這一點(diǎn)。
    4.3 條件變量
      前一節(jié)中我們講述了如何使用互斥鎖來實(shí)現(xiàn)線程間數(shù)據(jù)的共享和通信,互斥鎖一個(gè)明顯的缺點(diǎn)是它只有兩種狀態(tài):鎖定和非鎖定。而條件
    變量通過允許線程阻塞和等待另一個(gè)線程發(fā)送信號(hào)的方法彌補(bǔ)了互斥鎖的不足,它常和互斥鎖一起使用。使用時(shí),條件變量被用來阻塞一個(gè)線
    程,當(dāng)條件不滿足時(shí),線程往往解開相應(yīng)的互斥鎖并等待條件發(fā)生變化。一旦其它的某個(gè)線程改變了條件變量,它將通知相應(yīng)的條件變量喚醒
    一個(gè)或多個(gè)正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖并重新測(cè)試條件是否滿足。一般說來,條件變量被用來進(jìn)行線承間的同步

      條件變量的結(jié)構(gòu)為pthread_cond_t,函數(shù)pthread_cond_init()被用來初始化一個(gè)條件變量。它的原型為:
      extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));
      其中cond是一個(gè)指向結(jié)構(gòu)pthread_cond_t的指針,cond_attr是一個(gè)指向結(jié)構(gòu)pthread_condattr_t的指針。結(jié)構(gòu)pthread_condattr_t是條件
    變量的屬性結(jié)構(gòu),和互斥鎖一樣我們可以用它來設(shè)置條件變量是進(jìn)程內(nèi)可用還是進(jìn)程間可用,默認(rèn)值是 PTHREAD_
    PROCESS_PRIVATE,即此條件變量被同一進(jìn)程內(nèi)的各個(gè)線程使用。注意初始化條件變量只有未被使用時(shí)才能重新初始化或被釋放。釋放一個(gè)條件
    變量的函數(shù)為pthread_cond_ destroy(pthread_cond_t cond)。 
      函數(shù)pthread_cond_wait()使線程阻塞在一個(gè)條件變量上。它的函數(shù)原型為:
      extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
      pthread_mutex_t *__mutex));
      線程解開mutex指向的鎖并被條件變量cond阻塞。線程可以被函數(shù)pthread_cond_signal和函數(shù)
    pthread_cond_broadcast喚醒,但是要注意的是,條件變量只是起阻塞和喚醒線程的作用,具體的判斷條件還需用戶給出,例如一個(gè)變量是否
    為0等等,這一點(diǎn)我們從后面的例子中可以看到。線程被喚醒后,它將重新檢查判斷條件是否滿足,如果還不滿足,一般說來線程應(yīng)該仍阻塞在
    這里,被等待被下一次喚醒。這個(gè)過程一般用while語句實(shí)現(xiàn)。
      另一個(gè)用來阻塞線程的函數(shù)是pthread_cond_timedwait(),它的原型為:
      extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
      pthread_mutex_t *__mutex, __const struct timespec *__abstime));
      它比函數(shù)pthread_cond_wait()多了一個(gè)時(shí)間參數(shù),經(jīng)歷abstime段時(shí)間后,即使條件變量不滿足,阻塞也被解除。
      函數(shù)pthread_cond_signal()的原型為:
      extern int pthread_cond_signal __P ((pthread_cond_t *__cond));
      它用來釋放被阻塞在條件變量cond上的一個(gè)線程。多個(gè)線程阻塞在此條件變量上時(shí),哪一個(gè)線程被喚醒是由線程的調(diào)度策略所決定的。要
    注意的是,必須用保護(hù)條件變量的互斥鎖來保護(hù)這個(gè)函數(shù),否則條件滿足信號(hào)又可能在測(cè)試條件和調(diào)用pthread_cond_wait函數(shù)之間被發(fā)出,從
    而造成無限制的等待。下面是使用函數(shù)pthread_cond_wait()和函數(shù)pthread_cond_signal()的一個(gè)簡(jiǎn)單的例子。
    pthread_mutex_t count_lock;
    pthread_cond_t count_nonzero;
    unsigned count;
    decrement_count () {
    pthread_mutex_lock (&count_lock);
    while(count==0)
    pthread_cond_wait( &count_nonzero, &count_lock);
    count=count -1;
    pthread_mutex_unlock (&count_lock);
    }
    increment_count(){
    pthread_mutex_lock(&count_lock);
    if(count==0)
    pthread_cond_signal(&count_nonzero);
    count=count+1;
    pthread_mutex_unlock(&count_lock);
    }
      count值為0時(shí), decrement函數(shù)在pthread_cond_wait處被阻塞,并打開互斥鎖count_lock。此時(shí),當(dāng)調(diào)用到函數(shù)
    increment_count時(shí),pthread_cond_signal()函數(shù)改變條件變量,告知decrement_count()停止阻塞。讀者可以試著讓兩個(gè)線程分別運(yùn)行這
    兩個(gè)函數(shù),看看會(huì)出現(xiàn)什么樣的結(jié)果。
      函數(shù)pthread_cond_broadcast(pthread_cond_t
    *cond)用來喚醒所有被阻塞在條件變量cond上的線程。這些線程被喚醒后將再次競(jìng)爭(zhēng)相應(yīng)的互斥鎖,所以必須小心使用這個(gè)函數(shù)。
    4.4 信號(hào)量
      信號(hào)量本質(zhì)上是一個(gè)非負(fù)的整數(shù)計(jì)數(shù)器,它被用來控制對(duì)公共資源的訪問。當(dāng)公共資源增加時(shí),調(diào)用函數(shù)sem_post()增加信號(hào)量。只有
    當(dāng)信號(hào)量值大于0時(shí),才能使用公共資源,使用后,函數(shù)sem_wait()減少信號(hào)量。函數(shù)sem_trywait()和函數(shù)pthread_
    mutex_trylock()起同樣的作用,它是函數(shù)sem_wait()的非阻塞版本。下面我們逐個(gè)介紹和信號(hào)量有關(guān)的一些函數(shù),它們都在頭文件
    /usr/include/semaphore.h中定義。
      信號(hào)量的數(shù)據(jù)類型為結(jié)構(gòu)sem_t,它本質(zhì)上是一個(gè)長(zhǎng)整型的數(shù)。函數(shù)sem_init()用來初始化一個(gè)信號(hào)量。它的原型為:
      extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
      sem為指向信號(hào)量結(jié)構(gòu)的一個(gè)指針;pshared不為0時(shí)此信號(hào)量在進(jìn)程間共享,否則只能為當(dāng)前進(jìn)程的所有線程共享;value給出了信號(hào)量的
    初始值。
      函數(shù)sem_post( sem_t *sem
    )用來增加信號(hào)量的值。當(dāng)有線程阻塞在這個(gè)信號(hào)量上時(shí),調(diào)用這個(gè)函數(shù)會(huì)使其中的一個(gè)線程不在阻塞,選擇機(jī)制同樣是由線程的調(diào)度策略決定
    的。
      函數(shù)sem_wait( sem_t *sem
    )被用來阻塞當(dāng)前線程直到信號(hào)量sem的值大于0,解除阻塞后將sem的值減一,表明公共資源經(jīng)使用后減少。函數(shù)sem_trywait ( sem_t *sem
    )是函數(shù)sem_wait()的非阻塞版本,它直接將信號(hào)量sem的值減一。
      函數(shù)sem_destroy(sem_t *sem)用來釋放信號(hào)量sem。
      下面我們來看一個(gè)使用信號(hào)量的例子。在這個(gè)例子中,一共有4個(gè)線程,其中兩個(gè)線程負(fù)責(zé)從文件讀取數(shù)據(jù)到公共的緩沖區(qū),另兩個(gè)線程從
    緩沖區(qū)讀取數(shù)據(jù)作不同的處理(加和乘運(yùn)算)。
    /* File sem.c */
    #include
    #include
    #include
    #define MAXSTACK 100
    int stack[MAXSTACK][2];
    int size=0;
    sem_t sem;
    /* 從文件1.dat讀取數(shù)據(jù),每讀一次,信號(hào)量加一*/
    void ReadData1(void){
    FILE *fp=fopen("1.dat","r");
    while(!feof(fp)){
    fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
    sem_post(&sem);
    ++size;
    }
    fclose(fp);
    }
    /*從文件2.dat讀取數(shù)據(jù)*/
    void ReadData2(void){
    FILE *fp=fopen("2.dat","r");
    while(!feof(fp)){
    fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
    sem_post(&sem);
    ++size;
    }
    fclose(fp);
    }
    /*阻塞等待緩沖區(qū)有數(shù)據(jù),讀取數(shù)據(jù)后,釋放空間,繼續(xù)等待*/
    void HandleData1(void){
    while(1){
    sem_wait(&sem);
    printf("Plus:%d+%d=%dn",stack[size][0],stack[size][1],
    stack[size][0]+stack[size][1]);
    --size;
    }
    }
    void HandleData2(void){
    while(1){
    sem_wait(&sem);
    printf("Multiply:%d*%d=%dn",stack[size][0],stack[size][1],
    stack[size][0]*stack[size][1]);
    --size;
    }
    }
    int main(void){
    pthread_t t1,t2,t3,t4;
    sem_init(&sem,0,0);
    pthread_create(&t1,NULL,(void *)HandleData1,NULL);
    pthread_create(&t2,NULL,(void *)HandleData2,NULL);
    pthread_create(&t3,NULL,(void *)ReadData1,NULL);
    pthread_create(&t4,NULL,(void *)ReadData2,NULL);
    /* 防止程序過早退出,讓它在此無限期等待*/
    pthread_join(t1,NULL);
    }
      在Linux下,我們用命令gcc -lpthread sem.c -o sem生成可執(zhí)行文件sem。
    我們事先編輯好數(shù)據(jù)文件1.dat和2.dat,假設(shè)它們的內(nèi)容分別為1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10
    ,我們運(yùn)行sem,得到如下的結(jié)果:
    Multiply:-1*-2=2
    Plus:-1+-2=-3
    Multiply:9*10=90
    Plus:-9+-10=-19
    Multiply:-7*-8=56
    Plus:-5+-6=-11
    Multiply:-3*-4=12
    Plus:9+10=19
    Plus:7+8=15
    Plus:5+6=11
      從中我們可以看出各個(gè)線程間的競(jìng)爭(zhēng)關(guān)系。而數(shù)值并未按我們?cè)鹊捻樞蝻@示出來這是由于size這個(gè)數(shù)值被各個(gè)線程任意修改的緣故。這
    也往往是多線程編程要注意的問題。
    5 小結(jié)
      多線程編程是一個(gè)很有意思也很有用的技術(shù),使用多線程技術(shù)的網(wǎng)絡(luò)螞蟻是目前最常用的下載工具之一,使用多線程技術(shù)的grep比單線程
    的grep要快上幾倍,類似的例子還有很多。希望大家能用多線程技術(shù)寫出高效實(shí)用的好程序來。
    本文出自:http://www.china-pub.com 作者: 姚繼鋒 (2001-08-11 09:05:00)
    posted on 2009-02-25 14:53 allic 閱讀(479) 評(píng)論(0)  編輯  收藏 所屬分類: linux/UNIX 應(yīng)用開發(fā)C/C++
    主站蜘蛛池模板: 成年性生交大片免费看| 成在人线av无码免费高潮水 | 亚洲人成激情在线播放| 久久午夜羞羞影院免费观看| 亚洲精品卡2卡3卡4卡5卡区| 成人免费ā片在线观看| 亚洲乱码国产乱码精品精| 久久久久免费视频| 久久久久久a亚洲欧洲aⅴ| 人人揉揉香蕉大免费不卡| 亚洲男人天堂av| 毛片免费全部播放无码| 久久亚洲国产成人影院| 国产成人aaa在线视频免费观看| 亚洲日本一线产区和二线产区对比| 性感美女视频在线观看免费精品 | 亚洲精品成人无码中文毛片不卡| a级毛片视频免费观看| 亚洲国产精久久久久久久| 99视频全部免费精品全部四虎| 亚洲一区二区三区不卡在线播放| 18禁成年无码免费网站无遮挡| 亚洲成a人无码亚洲成www牛牛| 免费大片黄手机在线观看| 久久嫩草影院免费看夜色| 中文字幕亚洲精品| 女性无套免费网站在线看| 一级毛片a女人刺激视频免费| 亚洲第一AAAAA片| 日韩免费a级毛片无码a∨| 日本视频免费观看| 久久久无码精品亚洲日韩按摩| 中字幕视频在线永久在线观看免费| mm1313亚洲国产精品无码试看| 国产亚洲精品成人a v小说| 131美女爱做免费毛片| 亚洲a∨无码精品色午夜| 人人狠狠综合久久亚洲婷婷| 在线视频免费观看高清| www免费插插视频| 亚洲乱码一二三四区乱码|