歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

pthread

POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX標准。該標准定義了創建和操縱線程的一整套API。在類Unix操作系統(Unix、Linux、Mac OS X等)中,都使用Pthreads作為操作系統的線程。

1、pthread相關函數

#include //新建線程 int pthread_create(pthread_t *restrict tidp, constpthread_attr_t *restrict attr,void*(*start_rtn)(void*),void *restrict arg); //線程終止 void pthread_exit(void *rval_ptr);//線程自身主動退出 int pthread_join(pthread_t tid, void **rval_ptr);//其他線程阻塞自身,等待tid退出 //線程清理 voidpthread_cleanup_push(void(*rtn)(void*), void *arg); voidpthread_cleanup_pop(intexecute); 另: 操縱函數 pthread_create():創建一個線程 pthread_exit():終止當前線程 pthread_cancel():中斷另外一個線程的運行 pthread_join():阻塞當前的線程,直到另外一個線程運行結束 pthread_attr_init():初始化線程的屬性 pthread_attr_setdetachstate():設置脫離狀態的屬性(決定這個線程在終止時是否可以被結合) pthread_attr_getdetachstate():獲取脫離狀態的屬性 pthread_attr_destroy():刪除線程的屬性 pthread_kill():向線程發送一個信號

Pthread同步函數

用於 mutex 和條件變量 pthread_mutex_init() 初始化互斥鎖 pthread_mutex_destroy() 刪除互斥鎖 pthread_mutex_lock():占有互斥鎖(阻塞操作) pthread_mutex_trylock():試圖占有互斥鎖(不阻塞操作)。即,當互斥鎖空閒時,將占有該鎖;否則,立即返回。 pthread_mutex_unlock(): 釋放互斥鎖 pthread_cond_init():初始化條件變量 pthread_cond_destroy():銷毀條件變量 pthread_cond_signal(): 喚醒第一個調用pthread_cond_wait()而進入睡眠的線程 pthread_cond_wait(): 等待條件變量的特殊條件發生 Thread-local storage(或者以Pthreads術語,稱作線程特有數據): pthread_key_create(): 分配用於標識進程中線程特定數據的鍵 pthread_setspecific(): 為指定線程特定數據鍵設置線程特定綁定 pthread_getspecific(): 獲取調用線程的鍵綁定,並將該綁定存儲在 value 指向的位置中 pthread_key_delete(): 銷毀現有線程特定數據鍵 pthread_attr_getschedparam();獲取線程優先級 pthread_attr_setschedparam();設置線程優先級

Pthread工具函數

pthread_equal(): 對兩個線程的線程標識號進行比較 pthread_detach(): 分離線程 pthread_self(): 查詢線程自身線程標識號

2、pthread_create()線程創建

函數簡介

pthread_create是UNIX環境創建線程函數。創建線程(實際上就是確定調用該線程函數的入口點),在線程創建以後,就開始運行相關的線程函數。

頭文件

#include

函數聲明

int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);

返回值

若成功則返回0,否則返回出錯編號,返回-1。

參數

第一個參數為指向線程標識符的指針。

第二個參數用來設置線程屬性。

第三個參數是線程運行函數的起始地址。

最後一個參數是運行函數的參數。

另外

在編譯時注意加上-lpthread參數,以調用靜態鏈接庫。因為pthread並非Linux系統的默認庫

 

舉例:

/*thread.c*/
#include 
#include 

/*線程一*/
void thread_1(void)
{
    int i=0;
    for(i=0;i<=6;i++)
    {
        printf("This is a pthread_1.\n");
        if(i==2)
            pthread_exit(0);
        sleep(1);
    }
}

/*線程二*/
void thread_2(void)
{
    int i;
    for(i=0;i<3;i++)
        printf("This is a pthread_2.\n");
    pthread_exit(0);
}

int main(void)
{
    pthread_t id_1,id_2;
    int i,ret;
/*創建線程一*/
    ret=pthread_create(&id_1,NULL,(void  *) thread_1,NULL);
    if(ret!=0)
    {
        printf("Create pthread error!\n");
    return -1;
    }
/*創建線程二*/
     ret=pthread_create(&id_2,NULL,(void  *) thread_2,NULL);
    if(ret!=0)
    {
        printf("Create pthread error!\n");
    return -1;
    }
/*等待線程結束*/
    pthread_join(id_1,NULL);
    pthread_join(id_2,NULL);
    return 0;
}

以下是程序運行結果:

\

備注:pthread庫不是Linux系統默認的庫,連接時需要使用靜態庫libpthread.a,所以在線程函數在編譯時,需要連接庫函數,如上圖 gcc pthread_create.c -o pthread_create -lpthread

 

3、pthread_join函數

函數簡介

函數pthread_join用來等待一個線程的結束。

函數原型為:

extern int pthread_join __P (pthread_t __th, void **__thread_return);

參數:

第一個參數為被等待的線程標識符,第二個參數為一個用戶定義的指針,它可以用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為止,當函數返回時,被等待線程的資源被收回。如果執行成功,將返回0,如果失敗則返回一個錯誤號。

例子:

#include

#include

#include

struct member

{

int num;

char *name;

};

//結構體後的分號勿漏

void *create(void *arg)

//有void* 型參數傳入,不能直接void

{

struct member *temp;

temp=(struct member *)arg;

//結構體變量之間不能直接賦值,但可以通過指針賦地址

printf("member->num:%d\n",temp->num);

printf("member->name:%s\n",temp->name);

sleep(1);

return (void *)8;

 

//這個很有特色,返回一個指向void的數據類型的值,這個值作為後面的exit code

}

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

{

pthread_t tidp;

struct member *b;

void* a;

b=(struct member *)malloc(sizeof(struct member));

//先分配內存空間撒~

b->num=1;

b->name="mlq";

//字符串賦值,其他好用簡便的方法有: char *p = NULL; p = new char [256];

if((pthread_create(&tidp,NULL,create,(void*)b))==-1) /

//

void *

 

為“無類型指針”,void * 可以指向任何類型的數據

{

printf("create error!\n");

return 1;

}

 

4、pthread_detach()函數

pthread_detach(pthread_self())
linux線程執行和windows不同,pthread有兩種狀態joinable狀態和unjoinable狀態。創建一個線程默認的狀態是joinable。


如果線程是joinable狀態,當線程函數自己返回退出時或pthread_exit時都不會釋放線程所占用堆棧和線程描述符(總計8K多)。只有當你調用了pthread_join之後這些資源才會被釋放。但是調用pthread_join(pthread_id)後,如果該線程沒有運行結束,調用者會被阻塞,在有些情況下我們並不希望如此。
若是unjoinable狀態的線程,這些資源在線程函數退出時或pthread_exit時自動會被釋放。


unjoinable屬性可以在pthread_create時指定,或在線程創建後在線程中pthread_detach自己,如:pthread_detach(pthread_self()),將狀態改為unjoinable狀態,確保資源的釋放。或者將線程置為joinable,然後適時調用pthread_join.

其實簡單的說就是在線程函數頭加上 pthread_detach(pthread_self())的話,線程狀態改變,在函數尾部直接pthread_exit線程就會自動退出。省去了給線程擦屁股的麻煩

eg:

pthread_t tid;
int status = pthread_create(&tid, NULL, ThreadFunc, NULL);
if(status != 0)
{
perror("pthread_create error");
}
pthread_detach(tid);

 

如在Web服務器中當主線程為每個新來的鏈接創建一個子線程進行處理的時候,主線程並不希望因為調用pthread_join而阻塞(因為還要繼續處理之後到來的鏈接),這時可以在子線程中加入代碼

pthread_detach(pthread_self()) 或者父線程調用 pthread_detach(thread_id)(非阻塞,可立即返回) 這將該子線程的狀態設置為detached,則該線程運行結束後會自動釋放所有資源。

 

5、線程終止

a. 任一線程調用exit, _Exit, _exit都將導致整個進程終止;

b. 單個線程退出方式有三種:

  1> 線程執行函數start_rtn()中使用return返回,返回值為線程退出碼;

  2> 被同一個進程的其他線程使用pthread_cancel()取消;

  3> 線程自身調用了pthread_exit();

說明:pthread_join(pthread_t tid, void **rval_ptr)函數會阻塞調用線程,直到tid線程通過上述三種方式終止退出,且return/pthread_exit()方式會設置相應線程退出碼rval_ptr,而pthread_cancel()取消的線程,將退出碼設置為PTHREAD_CANCELED.

 

6、 線程清理處理程序(thread cleanup handler)

a> pthread_cleanup_push()與pthread_cleanup_pop()均為中實現的宏定義,具體實現如下:

? pthread_cleanup_push and pthread_cleanup_pop are macros and must always be used in matching pairs at the same nesting level of braces. */ # define pthread_cleanup_push(routine, arg) \ do{ \ __pthread_cleanup_class __clframe (routine, arg) /* Remove a cleanup handler installed by the matching pthread_cleanup_push. If EXECUTE is non-zero, the handler function is called. */ # define pthread_cleanup_pop(execute) \ __clframe.__setdoit (execute); \ }while(0)

可見push/pop中的{/}是一一對應的,因此pthread_cleanup_push/pop()也應一一對應出現,否則編譯出錯。

 

b> 當線程執行下列之一操作時調用清理函數,thread_cleanup_push由棧結構實現,注意清理程序調用的順序,先入後出。

  1: 調用pthread_exit()時,而直接return不會出發清理函數;

  2: 相應取消請求pthread_cancel()時;

  3: 使用非零execute參數調用pthread_cleanup_pop()時;

尤其需注意pthread_cleanup_pop()參數不同及此語句所處位置不同而有不同效果。

看此代碼實例,注意return或pthread_exit()位置不同導致pthread_cleanup_pop()不同參數的效果變化。

? #include void testPointerSize() { void*tret; printf("size of pointer in x86-64:%d\n",sizeof(tret)); //result is 8 in x86-64. //which is 4 in x86-32. printf("size of int in x86-64:%d\n",sizeof(int)); //result is 4 in x86-64. //which is also 4 in x86-32. } voidcleanup(void*arg) { printf("cleanup:%s\n",(char*)arg); } void* thr_fn1(void*arg) { printf("thread 1 start\n"); pthread_cleanup_push(cleanup,"thread 1 first handler"); pthread_cleanup_push(cleanup,"thread 1 second handler"); if(arg) return((void*)1);//arg !=0 ,return here. // return here will not triger any cleanup. pthread_cleanup_pop(0); pthread_cleanup_pop(1); return((void*)2);//will not run this } void* thr_fn2(void*arg) { printf("thread 2 start\n"); pthread_cleanup_push(cleanup,"thread 2 first handler"); pthread_cleanup_push(cleanup,"thread 2 second handler"); pthread_cleanup_pop(0); pthread_cleanup_pop(1); return((void*)2); // return here can triger cleanup second handler; } void* thr_fn3(void*arg) { printf("thread 3 start\n"); pthread_cleanup_push(cleanup,"thread 3 first handler"); pthread_cleanup_push(cleanup,"thread 3 second handler"); if(arg) pthread_exit((void*)3); //pthread_exit() here will triger both cleanup first&second handler. pthread_cleanup_pop(1); pthread_cleanup_pop(0); pthread_exit((void*)3);//wont run this } void* thr_fn4(void*arg) { printf("thread 4 start\n"); pthread_cleanup_push(cleanup,"thread 4 first handler"); pthread_cleanup_push(cleanup,"thread 4 second handler"); pthread_cleanup_pop(1); pthread_cleanup_pop(0); pthread_exit((void*)4); //pthread_exit() here will triger cleanup second handler. } int main(void) { testPointerSize(); interr; pthread_t tid1, tid2, tid3, tid4; void*tret; err = pthread_create(&tid1, NULL, thr_fn1, (void*)1); err = pthread_join(tid1,&tret); printf("thread 1 exit code %d\n",(int)tret); err = pthread_create(&tid2, NULL, thr_fn2, (void*)2); err = pthread_join(tid2, &tret); printf("thread 2 exit code %d\n",(int)tret); err = pthread_create(&tid3, NULL, thr_fn3, (void*)3); err = pthread_join(tid3,&tret); printf("thread 3 exit code %d\n",(int)tret); err = pthread_create(&tid4, NULL, thr_fn4, (void*)4); err = pthread_join(tid4, &tret); printf("thread 4 exit code %d\n",(int)tret); }

運行結果:

? [root@hello testData]# ./test size of pointer in x86-64:8 size of intin x86-64:4 thread1 start thread1exit code 1 thread2 start cleanup:thread2 first handler thread2exit code 2 thread3 start cleanup:thread3 second handler cleanup:thread3 first handler thread3exit code 3 thread4 start cleanup:thread4 second handler thread4exit code 4

由上述測試程序總結如下:

1> push與pop間的return,將導致清理程序不被觸發;

2> 位於pop之後return,由pop的參數確定是否觸發清理程序,非零參數觸發,零參數不觸發;

3> push/pop間的pthread_exit(),將觸發所有清理函數;

4>位於pop之後的pthread_exit()時,pop參數決定是否觸發清理程序;

其實,上述四種情況只是測試驗證了前文b所說三個條件,加深理解。

 

7、pthread_cleanup_push()/pthread_cleanup_pop()的詳解

一般來說,Posix的線程終止有兩種情況:正常終止和非正常終止。線程主動調用pthread_exit()或者從線程函數中return都將使線程正常退出,這是可預見的退出方式;非正常終止是線程在其他線程的干預下,或者由於自身運行出錯(比如訪問非法地址)而退出,這種退出方式是不可預見的。

 

不論是可預見的線程終止還是異常終止,都會存在資源釋放的問題,在不考慮因運行出錯而退出的前提下,如何保證線程終止時能順利的釋放掉自己所占用的資源,特別是鎖資源,就是一個必須考慮解決的問題。

最經常出現的情形是資源獨占鎖的使用:線程為了訪問臨界資源而為其加上鎖,但在訪問過程中被外界取消,如果線程處於響應取消狀態,且采用異步方式響應,或者在打開獨占鎖以前的運行路徑上存在取消點,則該臨界資源將永遠處於鎖定狀態得不到釋放。外界取消操作是不可預見的,因此的確需要一個機制來簡化用於資源釋放的編程。

在POSIX線程API中提供了一個pthread_cleanup_push()/pthread_cleanup_pop()函數對用於自動釋放資源 --從pthread_cleanup_push()的調用點到pthread_cleanup_pop()之間的程序段中的終止動作(包括調用 pthread_exit()和取消點終止)都將執行pthread_cleanup_push()所指定的清理函數。API定義如下:

void pthread_cleanup_push(void (*routine) (void  *),  void *arg)
void pthread_cleanup_pop(int execute)

pthread_cleanup_push()/pthread_cleanup_pop()采用先入後出的棧結構管理,void routine(void *arg)函數在調用pthread_cleanup_push()時壓入清理函數棧,多次對pthread_cleanup_push()的調用將在清理函數棧中形成一個函數鏈,在執行該函數鏈時按照壓棧的相反順序彈出。execute參數表示執行到pthread_cleanup_pop()時是否在彈出清理函數的同時執行該函數,為0表示不執行,非0為執行;這個參數並不影響異常終止時清理函數的執行。

pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式實現的,這是pthread.h中的宏定義:

#define pthread_cleanup_push(routine,arg)                                     
  { struct _pthread_cleanup_buffer _buffer;                                   
    _pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute)                                          
    _pthread_cleanup_pop (&_buffer, (execute)); }
可見,pthread_cleanup_push()帶有一個"{",而pthread_cleanup_pop()帶有一個"}",因此這兩個函數必須成對出現,且必須位於程序的同一級別的代碼段中才能通過編譯。在下面的例子裡,當線程在"do some work"中終止時,將主動調用pthread_mutex_unlock(mut),以完成解鎖動作。
work"中終止時,將主動調用pthread_mutex_unlock(mut),以完成解鎖動作。
pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
pthread_mutex_lock(&mut);
/* do some work */
pthread_mutex_unlock(&mut);
pthread_cleanup_pop(0);
必須要注意的是,如果線程處於PTHREAD_CANCEL_ASYNCHRONOUS狀態,上述代碼段就有可能出錯,因為CANCEL事件有可能在
pthread_cleanup_push()和pthread_mutex_lock()之間發生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之間發生,從而導致清理函數unlock一個並沒有加鎖的
mutex變量,造成錯誤。因此,在使用清理函數的時候,都應該暫時設置成PTHREAD_CANCEL_DEFERRED模式。為此,POSIX的
Linux實現中還提供了一對不保證可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()擴展函數,功能與以下
代碼段相當:
{ int oldtype;
 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
 pthread_cleanup_push(routine, arg);
 ...
 pthread_cleanup_pop(execute);
 pthread_setcanceltype(oldtype, NULL);
 }
Copyright © Linux教程網 All Rights Reserved