天天看點

Linux中線程使用詳解

linux下多線程詳解pdf文檔下載下傳:點選這裡!

線程與程序

為什麼有了程序的概念後,還要再引入線程呢?使用多線程到底有哪些好處?什麼的系統應該選用多線程?我們首先必須回答這些問題。

  使用多線程的理由之一是和程序相比,它是一種非常"節儉"的多任務操作方式。我們知道,在linux系統下,啟動一個新的程序必須配置設定給它獨立的位址空間,建立衆多的資料表來維護它的代碼段、堆棧段和資料段,這是一種"昂貴"的多任務工作方式。而運作于一個程序中的多個線程,它們彼此之間使用相同的位址空間,共享大部分資料,啟動一個線程所花費的空間遠遠小于啟動一個程序所花費的空間,而且,線程間彼此切換所需的時間也遠遠小于程序間切換所需要的時間。據統計,總的說來,一個程序的開銷大約是一個線程開銷的30倍左右,當然,在具體的系統上,這個資料可能會有較大的差別。

  使用多線程的理由之二是線程間友善的通信機制。對不同程序來說,它們具有獨立的資料空間,要進行資料的傳遞隻能通過通信的方式進行,這種方式不僅費時,而且很不友善。線程則不然,由于同一程序下的線程之間共享資料空間,是以一個線程的資料可以直接為其它線程所用,這不僅快捷,而且友善。當然,資料的共享也帶來其他一些問題,有的變量不能同時被兩個線程所修改,有的子程式中聲明為static的資料更有可能給多線程程式帶來災難性的打擊,這些正是編寫多線程程式時最需要注意的地方。

  除了以上所說的優點外,不和程序比較,多線程程式作為一種多任務、并發的工作方式,當然有以下的優點:

  1) 提高應用程式響應。這對圖形界面的程式尤其有意義,當一個操作耗時很長時,整個系統都會等待這個操作,此時程式不會響應鍵盤、滑鼠、菜單的操作,而使用多線程技術,将耗時長的操作(time consuming)置于一個新的線程,可以避免這種尴尬的情況。

  2) 使多cpu系統更加有效。作業系統會保證當線程數不大于cpu數目時,不同的線程運作于不同的cpu上。

3) 改善程式結構。一個既長又複雜的程序可以考慮分為多個線程,成為幾個獨立或半獨立的運作部分,這樣的程式會利于了解和修改。

一、線程辨別

線程有id, 但不是系統唯一, 而是程序環境中唯一有效.

線程的句柄是pthread_t類型, 該類型不能作為整數處理, 而是一個結構.

下面介紹兩個函數:

頭檔案: <pthread.h>

原型: int pthread_equal(pthread_t tid1, pthread_t tid2);

傳回值: 相等傳回非0, 不相等傳回0.

說明: 比較兩個線程id是否相等.

原型: pthread_t pthread_self();

傳回值: 傳回調用線程的線程id.

二、線程建立

 在執行中建立一個線程, 可以為該線程配置設定它需要做的工作(線程執行函數), 該線程共享程序的資源. 建立線程的函數pthread_create()

原型: int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg);

傳回值: 成功則傳回0, 否則傳回錯誤編号.

參數:

tidp: 指向新建立線程id的變量, 作為函數的輸出.

attr: 用于定制各種不同的線程屬性, null為預設屬性(見下).

start_rtn: 函數指針, 為線程開始執行的函數名.該函數可以傳回一個void *類型的傳回值,而這個傳回值也可以是其他類型,并由 pthread_join()擷取

arg: 函數的唯一無類型(void)指針參數, 如要傳多個參數, 可以用結構封裝.

linux下多線程程式的編譯方法:

       因為pthread的庫不是linux系統的庫,是以在進行編譯的時候要加上     -lpthread

       # gcc filename -lpthread  //預設情況下gcc使用c庫,要使用額外的庫要這樣選擇使用的庫

例:thread_create.c

#include <stdio.h>

#include <pthread.h>  //包線程要包含

void *mythread1(void)

{

   int i;

   for(i=0;i<100;i++)

   {

      printf("this is the 1st pthread,created by zieckey.\n");

      sleep(1);

   }

void *mythread2(void)

    int i;

for(i=0;i<100;i++)

      printf("this is the 2st pthread,created by zieckey.\n");

}

int main()

    int ret=0;

    pthread_tid1,id2;

   ret=pthread_create(&id1,null,(void*)mythread1,null);

    if(ret)

    {

        printf("create pthread error!\n");

         return -1; 

    }

   ret=pthread_create(&id2,null,(void*)mythread2,null);

         return  -1; 

   pthread_join(id1,null);

   pthread_join(id2,null);

    return 0;

編譯步驟:gcc thread_create .c -lpthread -othread_create

例2: thread_int.c  //向線程函數傳遞整形參數

#include <pthread.h>

#include <unistd.h>

void *create(void *arg)

    int *num;

    num=(int *)arg;

   printf("create parameter is %d \n",*num);

    return (void *)0;

int main(int argc,char *argv[])

   pthread_t tidp;

    int error;

    int test=4;

    int*attr=&test;

  error=pthread_create(&tidp,null,create,(void*)attr);

    if(error)

     {

       printf("pthread_create is created is not created...\n");

       return -1;

   sleep(1);

  printf("pthread_create is created...\n");

   return 0;

注:字元串,結構參數,一樣道理

三、線程屬性

 pthread_create()中的attr參數是一個結構指針,結構中的元素分别對應着新線程的運作屬性,主要包括以下幾項:

 __detachstate,表示新線程是否與程序中其他線程脫離同步,如果置位則新線程不能用pthread_join()來同步,且在退出時自行釋放所占用的資源。預設為pthread_create_joinable狀态。這個屬性也可以線上程建立并運作以後用pthread_detach()來設定,而一旦設定為pthread_create_detach狀态(不論是建立時設定還是運作時設定)則不能再恢複到 

pthread_create_joinable狀态。

__schedpolicy,表示新線程的排程政策,主要包括sched_other(正常、非實時)、sched_rr(實時、輪轉法)和  sched_fifo(實時、先入先出)三種,預設為sched_other,後兩種排程政策僅對超級使用者有效。運作時可以用過 

pthread_setschedparam()來改變。

__schedparam,一個struct sched_param結構,目前僅有一個sched_priority整型變量表示線程的運作優先級。這個參數僅當排程政策為實時(即sched_rr或sched_fifo)時才有效,并可以在運作時通過pthread_setschedparam()函數來改變,預設為0。

__inheritsched,有兩種值可供選擇:pthread_explicit_sched和pthread_inherit_sched,前者表示新線程使用顯式指定排程政策和排程參數(即attr中的值),而後者表示繼承調用者線程的值。預設為pthread_explicit_sched。

 __scope,表示線程間競争cpu的範圍,也就是說線程優先級的有效範圍。posix的标準中定義了兩個值: 

pthread_scope_system和pthread_scope_process,前者表示與系統中所有線程一起競争cpu時間,後者表示僅與同程序中的線程競争cpu。目前linuxthreads僅實作了pthread_scope_system一值。

pthread_attr_t結構中還有一些值,為了設定這些屬性,posix定義了一系列屬性設定函數,包括pthread_attr_init()、 pthread_attr_destroy()和與各個屬性相關的pthread_attr_get(),pthread_attr_set()函數。

  pthread_create()中,第二個參數(pthread_attr_t)為将要建立的thread屬性。通常情況下配置為null,使用預設設定就可以了。但了解這些屬性,有利于更好的了解thread.

屬性對象(pthread_attr_t)是不透明的,而且不能通過指派直接進行修改。系統提供了一組函數,用于初始化、配置和銷毀每種對象類型。

 建立屬性:

int pthread_attr_init(pthread_attr_t *attr);

建立的屬性設定為預設設定。

 銷毀屬性:

int pthread_attr_destroy(pthread_attr_t *attr);

一:設定分離狀态:

線程的分離狀态有2種:pthread_create_joinable(非分離狀态), pthread_create_detached(分離狀态)

分離狀态含義如下:

如果使用 pthread_create_joinable 建立非分離線程,則假設應用程式将等待線程完成。也就是說,程式将對線程執行 pthread_join。 非分離線程在終止後,必須要有一個線程用 join 來等待它。否則,不會釋放該線程的資源以供新線程使用,而這通常會導緻記憶體洩漏。是以,如果不希望線程被等待,請将該線程作為分離線程來建立。

如果使用 pthread_create_detached 建立分離thread,則表明此thread在退出時會自動回收資源和thread

id.

sam之前很喜歡使用分離thread. 但現在慢慢使用中覺得這樣是個不好的習慣。因為分離thread有個問題:主程式退出時,很難确認子thread已經退出。隻好使用全局變量來标明子thread已經正常退出了。

另外:不管建立分離還是非分離的thread.在子thread全部退出之前退出主程式都是很有風險的。如果主thread選擇return,或者調用exit()退出,則所有thread都會被kill掉。這樣很容易出錯。sam上次出的問題其實就是這個。但如果主thread隻是調用pthread_exit().則僅主線程本身終止。程序及程序内的其他線程将繼續存在。所有線程都已終止時,程序也将終止。

intpthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate);

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

得到目前和分離狀态和設定目前的分離狀态。

二:設定棧溢出保護區大小:

棧溢出概念:

·                     溢出保護可能會導緻系統資源浪費。如果應用程式建立大量線程,并且已知這些線程永遠不會溢出其棧,則可以關閉溢出保護區。通過關閉溢出保護區,可以節省系統資源。

·                     線程在棧上配置設定大型資料結構時,可能需要較大的溢出保護區來檢測棧溢出。

int pthread_attr_getguardsize(const pthread_attr_t *restrictattr,size_t *restrict guardsize);

int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);

設定和得到棧溢出保護區。如果guardsize設為0。則表示不設定棧溢出保護區。guardsize 的值向上舍入為pagesize 的倍數。

三:設定thread競用範圍:

競用範圍(pthread_scope_system 或 pthread_scope_process)指 使用 pthread_scope_system 時,此線程将與系統中的所有線程進行競争。使用 pthread_scope_process 時,此線程将與程序中的其他線程進行競争。

int pthread_attr_getscope(const pthread_attr_t *restrict attr,int*restrict contentionscope);

int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);

四:設定線程并行級别:

int pthread_getconcurrency(void);

int pthread_setconcurrency(int new_level);

sam不了解這個意思。

五:設定排程政策:

posix 标準指定 sched_fifo(先入先出)、sched_rr(循環)或 sched_other(實作定義的方法)的排程政策屬性。

·                     sched_fifo

如果調用程序具有有效的使用者 id 0,則争用範圍為系統 (pthread_scope_system) 的先入先出線程屬于實時 (rt) 排程類。如果這些線程未被優先級更高的線程搶占,則會繼續處理該線程,直到該線程放棄或阻塞為止。對于具有程序争用範圍 (pthread_scope_process)) 的線程或其調用程序沒有有效使用者

id 0 的線程,請使用 sched_fifo。sched_fifo 基于 ts 排程類。

·                     sched_rr

如果調用程序具有有效的使用者 id 0,則争用範圍為系統 (pthread_scope_system)) 的循環線程屬于實時 (rt) 排程類。如果這些線程未被優先級更高的線程搶占,并且這些線程沒有放棄或阻塞,則在系統确定的時間段内将一直執行這些線程。對于具有程序争用範圍 (pthread_scope_process)

的線程,請使用sched_rr(基于 ts 排程類)。此外,這些線程的調用程序沒有有效的使用者id 0。

sched_fifo 是基于隊列的排程程式,對于每個優先級都會使用不同的隊列。sched_rr 與 fifo 相似,不同的是前者的每個線程都有一個執行時間配額。

int pthread_attr_getschedpolicy(const pthread_attr_t *restrictattr,int *restrict policy);

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

六:設定優先級:

int pthread_attr_getschedparam(const pthread_attr_t *restrictattr,struct sched_param *restrict param);

int pthread_attr_setschedparam(pthread_attr_t *restrict attr,

              conststruct sched_param *restrict param);

比較複雜,sam沒去研究。

七:設定棧大小:

當建立一個thread時,會給它配置設定一個棧空間,線程棧是從頁邊界開始的。任何指定的大小都被向上舍入到下一個頁邊界。不具備通路權限的頁将被附加到棧的溢出端(第二項設定中設定)。

指定棧時,還應使用 pthread_create_joinable 建立線程。在該線程的 pthread_join() 調用傳回之前,不會釋放該棧。在該線程終止之前,不會釋放該線程的棧。了解這類線程是否已終止的唯一可靠方式是使用pthread_join。

一般情況下,不需要為線程配置設定棧空間。系統會為每個線程的棧配置設定指定大小的虛拟記憶體。

#ulimit -a可以看到這個預設大小

 四、線程終止

如果程序中的任一線程調用了exit,_exit或者_exit,那麼整個程序就會終止。與此類似,如果信号的預設動作是終止程序,那麼,把該信号發送到線程會終止整個程序。

單個線程可以通過下列三種方式退出,在不終止整個程序的情況下停止它的控制流。

(1):從啟動例程中傳回,傳回值是線程的退出碼

(2):線程可以被同一程序中的其他線程取消

(3):線程調用pthread_exit()

pthread_exit函數:

原型: void pthread_exit(void *rval_ptr);

參數: rval_ptr是一個無類型指針, 指向線程的傳回值存儲變量.

 pthread_join函數:

原型: int pthread_join(pthread_t thread, void **rval_ptr);

thread: 線程id.

rval_ptr: 指向傳回值的指針(傳回值也是個指針).

說明:

調用線程将一直阻塞, 直到指定的線程調用pthread_exit, 從啟動例程傳回或被取消.

如果線程從它的啟動例程傳回, rval_ptr包含傳回碼.

如果線程被取消, 由rval_ptr指定的記憶體單元置為: pthread_canceled.

如果對傳回值不關心, 可把rval_ptr設為null.

執行個體:

/* print process and thread ids */

void printids(const char *s)

   pid_t pid, ppid;

   pthread_t tid;

     pid= getpid();

   ppid = getppid();

   tid = pthread_self();

    printf("%16s pid %5u ppid %5u tid %16u (0x%x) ",

            s, (unsigned int)pid, (unsigned int)ppid,

            (unsigned int)tid, (unsigned int)tid);

 /* thread process */

void *thread_func(void *arg);

   printids("new thread: ");

   return (void *)108;

 /* main func */

   int err;

   void *tret; /* thread return value */

   pthread_t ntid;

   err = pthread_create(&ntid, null, thread_func, null);

   if (err != 0)

       perror("can't create thread");

   err = pthread_join(ntid, &tret);

       perror("can't join thread");

    printids("main thread: ");

   printf("thread exit code: %d ", (int)tret);

   sleep(1);

這段代碼是通過前一個執行個體改編的, 執行流程如下:

首先建立一個新線程, 該線程在列印完ids後, 傳回108.

然後用pthread_join擷取該線程傳回值, 存于tret中.

主線程列印ids.

主線程列印tret, 即新線程的傳回值.

線程取消的定義:

一般情況下,線程在其主體函數退出的時候會自動終止,但同時也可以因為接收到另一個線程發來的終止(取消)請求而強制終止。

線程取消的語義:

線程取消的方法是向目标線程發cancel信号,但如何處理cancel信号則由目标線程自己決定,或者忽略、或者立即終止、或者繼續運作至cancelation-point(取消點),由不同的cancelation狀态決定。

線程接收到cancel信号的預設處理(即pthread_create()建立線程的預設狀态)是繼續運作至取消點,也就是說設定一個canceled狀态,線程繼續運作,隻有運作至cancelation-point的時候才會退出。

取消點定義:

根據posix标準,pthread_join()、pthread_testcancel()、pthread_cond_wait()、  pthread_cond_timedwait()、sem_wait()、sigwait()等函數以及read()、write()等會引起阻塞的系統調用都是cancelation-point,而其他pthread函數都不會引起cancelation動作。但是pthread_cancel的手冊頁聲稱,由于linuxthread庫與c庫結合得不好,因而目前c庫函數都不是cancelation-point;但cancel信号會使線程(http://blog.csdn.net/shanzhizi)從阻塞的系統調用中退出,并置eintr錯誤碼,是以可以在需要作為cancelation-point的系統調用前後調用 

pthread_testcancel(),進而達到posix标準所要求的目标,即如下代碼段:

pthread_testcancel();

   retcode = read(fd, buffer, length);

   pthread_testcancel(); 

程式設計方面的考慮:

如果線程處于無限循環中,且循環體内沒有執行至取消點的必然路徑,則線程無法由外部其他線程的取消請求而終止。是以在這樣的循環體的必經路徑上應該加入pthread_testcancel()調用。即如下代碼段:

while(1)

    ………

    pthread_testcancel();

與線程取消相關的pthread函數:

intpthread_cancel(pthread_t thread):線程可以通過調用pthread_cancel函數來請求取消同一程序中的其他程序。

發送終止信号給thread線程,如果成功則傳回0,否則為非0值。發送成功并不意味着thread會終止。注意pthread_cancel并不等待線程終止,它僅僅提出請求。

 int pthread_setcancelstate(int state, int*oldstate):

設定本線程對cancel信号的反應,state有兩種值:pthread_cancel_enable(預設)和  pthread_cancel_disable,分别表示收到信号後設為cancled狀态和忽略cancel信号繼續運作;old_state如果不為 null則存入原來的cancel狀态以便恢複。

int pthread_setcanceltype(int type, int*oldtype)

設定本線程取消動作的執行時機,type由兩種取值:pthread_cancel_deffered和  pthread_cancel_asychronous,僅當cancel狀态為enable時有效,分别表示收到信号後繼續運作至下一個取消點再退出和立即執行取消動作(退出);oldtype如果不為null則存入原來的取消動作類型值。

void pthread_testcancel(void)

檢查本線程是否處于canceld狀态,如果是,則進行取消動作,否則直接傳回。

 http://blog.csdn.net/shanzhizi

線程可以安排它退出時需要調用的函數,這與程序可以用atexit函數安排程序退出時需要調用的函數是類似的。線程可以建立多個清理處理程式。處理程式記錄在棧中,也就是說它們的執行順序與它們注冊時的順序相反。

void pthread_cleanup_push(void(*rtn)(void*),void *arg);

void pthread_cleanup_pop(int execute);

當線程執行以下動作時調用清理函數,調用參數為arg,清理函數的調用順序用pthread_cleanup_push來安排。

調用pthread_exit時

響應取消請求時

用非0的execute參數調用pthread_cleanup_pop時。

如果線程是通過從它的啟動例程中傳回而終止的話,那麼它的清理處理程式就不會被調用,還要注意清理處理程式是按照與它們安裝時相反的順序被調用的。

int pthread_detach(pthread_t tid);

可以用于使線程進入分離狀态。

繼續閱讀