1. 程式人生 > >pthread常用API及簡單介紹

pthread常用API及簡單介紹

經過了上篇文件的初步學習,對pthread有了一個簡單的感性認識,但是對pthread的認識還是比較少,在這篇文件當中將要主要學習pthread的一些常用的API。

首先是pthread的執行緒建立API: pthread_create

#include<pthread.h>  
int pthread_create(pthread_t *thread,//要建立的執行緒  
                                 pthread_attr_t *attr,//執行緒的相關屬性  
                                 void* (*start_routine)(void*),//執行緒要執行的函式指標  
                                 void *arg//傳遞給start_routine的引數  
                                 );   

當建立函式執行成功的時候返回0,並把建立的執行緒tid寫入傳入的執行緒指標中去(第一個引數),否則返回一個非零值並設定errno。

其中,第一個和第三個引數是必須要設定的,其他兩個引數可以根據情況設定,當沒有需求的時候傳入NULL即可。

pthread_exit: 終止當前執行緒

#include<pthread.h>  
void pthread_exit(void *retval);  
//該函式用於退出當前執行緒,退出之前將呼叫pthread_cleanup_push  
//上述函式將在下文中介紹。  
//該函式線上程的上層函式中是被隱式呼叫的,可以增加一個retval引數  
//顯示呼叫,以供pthread_join函式參考 

pthread_join:掛起當前執行緒直到指定的執行緒終止為止(這個函式對於pthread十分重要,正如上一篇文件中所說的,不呼叫該函式可能會造成後續的create失敗的問題)。

#include<pthread.h>  
int pthread_join(pthread_t thread, void **thread_return);  
//thread_return為th終止時的引數,  
//沒有顯式指定則為NULL  
//需要查閱該引數的使用方法  

pthread_cancel: 撤銷一個執行緒

#include<pthread.h>  
//撤銷執行緒thread  
int pthread_cancel(pthread_t thread);  
//用於設定當前執行緒的撤銷狀態  
//PTHREAD_CANCEL_ENABLE允許撤銷  
//PTHREAD_CANCEL_DISABLE忽略撤銷  
//為了避免被另外的執行緒用pthread_cancel撤銷  
//oldstate用來儲存之前的狀態,以便恢復  
int pthread_setcancelstate(int state, int *oldstate);  
//設定當前執行緒的撤銷型別,包括:  
//PTHREAD_CANCEL_ASYNCHRONOUS立即撤銷  
//PTHREAD_CANCEL_DEFERRED 延遲至撤銷點  
int pthread_setcanceltype(int type, int *oldtype);  

執行緒屬性:(上文中create函式的第二個引數,型別為pthread_attr_t,可以使用pthread_attr_XXXX函式族呼叫)

detachstate:分離或者切入狀態,有兩個值PTHREAD_CREATE_JOINABLE(default value),PHTREAD_CREATE_DETACHED

schedpolicy:  排程策略,取值有: SCHED_OTHER, SCHED_FIFO

schedparam:  排程策略相關

inheritsched: PTHREAD_EXPLICIT_SCHED(default value), PTHREAD_INERIT_SCHED

scope: 時間片,取值有:PTHREAD_SCOPE_SYSTEM(default value每個執行緒 一個系統時間片), PTHREAD_SCOPE_PROGCESS(執行緒共享系統時間片)。

pthread cleanup巨集

pthread cleanup巨集主要用來處理執行緒的推出狀態,pthread_exit和pthread_join等可以作為它的引數

#include<pthread.h>  
//這些都是巨集定義,相關細節可以檢視/usr/include/pthread.h  
void pthread_cleanup_push( void(*routine)(void*), void *arg);  
void pthread_cleanup_pop(int execute);  
void pthread_cleanup_push_defer_np(void(*routine)(void*), void *arg);  
void pthread_cleanup_pop_restore_np(int execute);  
//對這些巨集的理解並不深入,在後續的學習中參考相應的例子  
//進一步的學習。  
//這些巨集用於執行緒結束時釋放相關資源,用pthread_exit呼叫pthread_cleanup  
//進行處理時,用完後要用對應的pthread_cleanup_pop從棧中彈出  

互斥mutex

由於執行緒是併發執行的,因此有時候需要對一些資料進行保護,例如多執行緒標準輸出如果不加以處理,那麼輸出基本上都會是亂碼,此時就可以使用mutex對輸出流進行控制,一個執行緒執行寫入操作的時候,加一把互斥鎖,以防別的執行緒同時寫入。

互斥物件在pthread中的定義為pthread_mutex_t,以下是它的一些API函式:

#include<pthread.h>  
//建立互斥物件,用指定的初始化屬性初始化互斥物件  
//屬性包括:  
//PTHREAD_MUTEX_INITIALIZER 快速互斥,簡單加解鎖  
//PTHREAD_RECURSIVE_MUTEX_INITIALIZER  
//遞迴互斥,給加鎖計數,解鎖時需要同樣次數的pthread_mutex_unlock  
//PTHREAD_ERRORCHECK_MUTEX_INITIALIZER  
//建立檢錯互斥,這種互斥在被鎖之後會試圖給它加鎖的執行緒返回一個  
//EDEADLK錯誤程式碼而不阻塞  
int pthread_mutex_init(pthread_mutex_t *mutex,  
                                       const pthread_mutex_attr_t *mutexattr);  
//加鎖  
int pthread_mutex_lock(pthread_mutex_t *mutex);  
//解鎖  
int pthread_mutex_unlock(pthread_mutex_t *mutex);  
//加鎖,但是如果物件已經上鎖則返回EBUSY錯誤程式碼而不阻塞  
int pthread_mmutex_trylock(pthread_mutex_t *mutex);  
//析構並釋放mutex相關資源  
int pthread_mutex_destroy(pthread_mutex_t *mutex);  
//以上的函式成功均返回0,和一般的函式一致  

條件變數

執行緒使用條件變數物件來阻塞自己以等待某個特定條件的發生。

條件物件的定義為pthread_cond_t

#include<pthread.h>  
//建立指向條件變數的指標,在linux下可以使用PTHREAD_COND_INITIALIZER  
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;  
int pthread_cond_init(pthread_cond_t *cond,   
                                        pthread_condattr_t *cond_attr);  
//析構  
int pthread_cond_destroy(pthread_cond_t *cond);  
//兩個訊號函式  
int pthread_cond_singal(pthread_cond_t *cond);  
int pthread_cond_broadcast(pthread_cond_t *cond);  
//兩個等待條件否則掛起的函式  
//當前期望的條件沒有到來的時候掛起執行緒,解鎖mutex  
//條件成立的時候喚起執行緒,鎖定互斥  
int pthread_cond_wait(pthread_cond_t *cond,  
                                        pthread_mutex_t *mutex);  
//abstime引數和相容time()返回值的絕對時間,  
//UNIX紀元時間1970-01-01起至今的秒數  
//如果該時間前條件未發生,則結束並返回ETIMEOUT錯誤  
int pthread_cond_wimewait(pthread_cond_t *cond,  
                                                  pthread_mutex_t *mutex,  
                                                  const struct timespec *abstime);  
//上述兩個函式都將被設定為撤銷點(這個撤銷點的意義?)  

至此,基本的pthread的API函式就學習到這裡了,在後續的學習當中,將會結合例子更深入的學習。、

==================================================================================================================

1 Introduction
不用介紹了吧…
2 Thread Concepts
1.     Thread由下面部分組成:
a.     Thread ID
b.     Stack
c.     Policy
d.     Signal mask
e.     Errno
f.      Thread-Specific Data
3 Thread Identification
1.     pthread_t用於表示Thread ID,具體內容根據實現的不同而不同,有可能是一個Structure,因此不能將其看作為整數
2.     pthread_equal函式用於比較兩個pthread_t是否相等
#i nclude <pthread.h>
 
int pthread_equal(pthread_t tid1, pthread_t tid2)


3.     pthread_self函式用於獲得本執行緒的thread id
#i nclude <pthread.h>
 
pthread _t pthread_self(void);


 
4 Thread Creation
1.     建立執行緒可以呼叫pthread_create函式:
#i nclude <pthread.h>
 
int pthread_create(
       pthread_t *restrict tidp,
       const pthread_attr_t *restrict attr,
       void *(*start_rtn)(void *), void *restrict arg);


a.     pthread_t *restrict tidp:返回最後創建出來的Thread的Thread ID
b.     const pthread_attr_t *restrict attr:指定執行緒的Attributes,後面會講道,現在可以用NULL
c.     void *(*start_rtn)(void *):指定執行緒函式指標,該函式返回一個void *,引數也為void*
d.     void *restrict arg:傳入給執行緒函式的引數
e.     返回錯誤值。
2.     pthread函式在出錯的時候不會設定errno,而是直接返回錯誤值
3.     在Linux系統下面,在老的核心中,由於Thread也被看作是一種特殊,可共享地址空間和資源的Process,因此在同一個Process中建立的不同Thread具有不同的Process ID(呼叫getpid獲得)。而在新的2.6核心之中,Linux採用了NPTL(Native POSIX Thread Library)執行緒模型(可以參考http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library和http://www-128.ibm.com/developerworks/linux/library/l-threading.html?ca=dgr-lnxw07LinuxThreadsAndNPTL),在該執行緒模型下同一程序下不同執行緒呼叫getpid返回同一個PID。
4.     不能對建立的新執行緒和當前建立者執行緒的執行順序作出任何假設
5 Thread Termination
1.     exit, _Exit, _exit用於中止當前程序,而非執行緒
2.     中止執行緒可以有三種方式:
a.     線上程函式中return
b.     被同一程序中的另外的執行緒Cancel掉
c.     執行緒呼叫pthread_exit函式
3.     pthread_exit和pthread_join函式的用法:
a.     執行緒A呼叫pthread_join(B, &rval_ptr),被Block,進入Detached狀態(如果已經進入Detached狀態,則pthread_join函式返回EINVAL)。如果對B的結束程式碼不感興趣,rval_ptr可以傳NULL。
b.     執行緒B呼叫pthread_exit(rval_ptr),退出執行緒B,結束程式碼為rval_ptr。注意rval_ptr指向的記憶體的生命週期,不應該指向B的Stack中的資料。
c.     執行緒A恢復執行,pthread_join函式呼叫結束,執行緒B的結束程式碼被儲存到rval_ptr引數中去。如果執行緒B被Cancel,那麼rval_ptr的值就是PTHREAD_CANCELLED。
兩個函式原型如下:
#i nclude <pthread.h>
 
void pthread_exit(void *rval_ptr);
 
int pthread_join(pthread_t thread, void **rval_ptr);


4.     一個Thread可以要求另外一個Thread被Cancel,通過呼叫pthread_cancel函式:
#i nclude <pthread.h>
 
void pthread_cancel(pthread_t tid)


該函式會使指定執行緒如同呼叫了pthread_exit(PTHREAD_CANCELLED)。不過,指定執行緒可以選擇忽略或者進行自己的處理,在後面會講到。此外,該函式不會導致Block,只是傳送Cancel這個請求。
5.     執行緒可以安排在它退出的時候,某些函式自動被呼叫,類似atexit()函式。需要呼叫如下函式:
#i nclude <pthread.h>
 
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);


這兩個函式維護一個函式指標的Stack,可以把函式指標和函式引數值push/pop。執行的順序則是從棧頂到棧底,也就是和push的順序相反。
在下面情況下pthread_cleanup_push所指定的thread cleanup handlers會被呼叫:
a.     呼叫pthread_exit
b.     相應cancel請求
c.     以非0引數呼叫pthread_cleanup_pop()。(如果以0呼叫pthread_cleanup_pop(),那麼handler不會被呼叫
有一個比較怪異的要求是,由於這兩個函式可能由巨集的方式來實現,因此這兩個函式的呼叫必須得是在同一個Scope之中,並且配對,因為在pthread_cleanup_push的實現中可能有一個{,而pthread_cleanup_pop可能有一個}。因此,一般情況下,這兩個函式是用於處理意外情況用的,舉例如下:
void *thread_func(void *arg)
{
    pthread_cleanup_push(cleanup, “handler”)
 
    // do something
 
    Pthread_cleanup_pop(0);
    return((void *)0);
}


 
6.     程序函式和執行緒函式的相關性:
Process Primitive Thread Primitive Description
fork pthread_create 建立新的控制流
exit pthread_exit 退出已有的控制流
waitpid pthread_join 等待控制流並獲得結束程式碼
atexit pthread_cleanup_push 註冊在控制流退出時候被呼叫的函式
getpid pthread_self 獲得控制流的id
abort pthread_cancel 請求非正常退出


7.     預設情況下,一個執行緒A的結束狀態被儲存下來直到pthread_join為該執行緒被呼叫過,也就是說即使執行緒A已經結束,只要沒有執行緒B呼叫pthread_join(A),A的退出狀態則一直被儲存。而當執行緒處於Detached狀態之時,黨執行緒退出的時候,其資源可以立刻被回收,那麼這個退出狀態也丟失了。在這個狀態下,無法為該執行緒呼叫pthread_join函式。我們可以通過呼叫pthread_detach函式來使指定執行緒進入Detach狀態:
#i nclude <pthread.h>
 
int pthread_detach(pthread_t tid);


通過修改呼叫pthread_create函式的attr引數,我們可以指定一個執行緒在建立之後立刻就進入Detached狀態
6 Thread Synchronization
1.     互斥量:Mutex
a.     用於互斥訪問
b.     型別:pthread_mutex_t,必須被初始化為PTHREAD_MUTEX_INITIALIZER(用於靜態分配的mutex,等價於pthread_mutex_init(…, NULL))或者呼叫pthread_mutex_init。Mutex也應該用pthread_mutex_destroy來銷燬。這兩個函式原型如下:(attr的具體含義下一章討論)
#i nclude <pthread.h>
 
int pthread_mutex_init(
       pthread_mutex_t *restrict mutex,
       const pthread_mutexattr_t *restrict attr)
 
int pthread_mutex_destroy(pthread_mutex_t *mutex);


c.     pthread_mutex_lock用於Lock Mutex,如果Mutex已經被Lock,該函式呼叫會Block直到Mutex被Unlock,然後該函式會Lock Mutex並返回。pthread_mutex_trylock類似,只是當Mutex被Lock的時候不會Block,而是返回一個錯誤值EBUSY。pthread_mutex_unlock則是unlock一個mutex。這三個函式原型如下:
#i nclude <pthread.h>
 
int pthread_mutex_lock(pthread_mutex_t *mutex);
 
int pthread_mutex_trylock(pthread_mutex_t *mutex);
 
int pthread_mutex_unlock(pthread_mutex_t *mutex);


 
2.     讀寫鎖:Reader-Writer Locks
a.     多個執行緒可以同時獲得讀鎖(Reader-Writer lock in read mode),但是隻有一個執行緒能夠獲得寫鎖(Reader-writer lock in write mode)
b.     讀寫鎖有三種狀態
                                          i.    一個或者多個執行緒獲得讀鎖,其他執行緒無法獲得寫鎖
                                         ii.    一個執行緒獲得寫鎖,其他執行緒無法獲得讀鎖
                                        iii.    沒有執行緒獲得此讀寫鎖
c.     型別為pthread_rwlock_t
d.     建立和關閉方法如下:
#i nclude <pthread.h>
 
int pthread_rwlock_init(
       pthread_rwlock_t *restrict rwlock,
       const pthread_rwlockattr_t *restrict attr)
 
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);


e.     獲得讀寫鎖的方法如下:
#i nclude <pthread.h>
 
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
 
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
 
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
 
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
 
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);


 
pthread_rwlock_rdlock:獲得讀鎖
pthread_rwlock_wrlock:獲得寫鎖
pthread_rwlock_unlock:釋放鎖,不管是讀鎖還是寫鎖都是呼叫此函式
注意具體實現可能對同時獲得讀鎖的執行緒個數有限制,所以在呼叫pthread_rwlock_rdlock的時候需要檢查錯誤值,而另外兩個pthread_rwlock_wrlock和pthread_rwlock_unlock則一般不用檢查,如果我們程式碼寫的正確的話。
3.     Conditional Variable:條件
a.     條件必須被Mutex保護起來
b.     型別為:pthread_cond_t,必須被初始化為PTHREAD_COND_INITIALIZER(用於靜態分配的條件,等價於pthread_cond_init(…, NULL))或者呼叫pthread_cond_init
#i nclude <pthread.h>
 
int pthread_cond_init(
       pthread_cond_t *restrict cond,
       const pthread_condxattr_t *restrict attr)
 
int pthread_cond_destroy(pthread_cond_t *cond);


 
c.     pthread_cond_wait函式用於等待條件發生(=true)。pthread_cond_timedwait類似,只是當等待超時的時候返回一個錯誤值ETIMEDOUT。超時的時間用timespec結構指定。此外,兩個函式都需要傳入一個Mutex用於保護條件
#i nclude <pthread.h>
 
int pthread_cond_wait(
       pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex);
 
int pthread_cond_timedwait(
       pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex,
       const struct timespec *restrict timeout);


 
d.     timespec結構定義如下:
struct timespec {
       time_t tv_sec;      
       long   tv_nsec;     
};


注意timespec的時間是絕對時間而非相對時間,因此需要先呼叫gettimeofday函式獲得當前時間,再轉換成timespec結構,加上偏移量。
e.     有兩個函式用於通知執行緒條件被滿足(=true):
#i nclude <pthread.h>
 
int pthread_cond_signal(pthread_cond_t *cond);
 
int pthread_cond_broadcast(pthread_cond_t *cond);


兩者的區別是前者會喚醒單個執行緒,而後者會喚醒多個執行緒。


補充:

在傳統的Unix模型中,當一個程序需要由另一個實體執行某件事時,該程序派生(fork)一個子程序,讓子程序去進行處理。Unix下的大多數網路伺服器程式都是這麼編寫的,即父程序接受連線,派生子程序,子程序處理與客戶的互動。

雖然這種模型很多年來使用得很好,但是fork時有一些問題:

1. fork是昂貴的。記憶體映像要從父程序拷貝到子程序,所有描述字要在子程序中複製等等。目前有的Unix實現使用一種叫做寫時拷貝(copy-on-write)的技術,可避免父程序資料空間向子程序的拷貝。儘管有這種優化技術,fork仍然是昂貴的。

2. fork子程序後,需要用程序間通訊(IPC)在父子程序之間傳遞資訊。Fork之前的資訊容易傳遞,因為子程序從一開始就有父程序資料空間及所有描述字的拷貝。但是從子程序返回資訊給父程序需要做更多的工作。

執行緒有助於解決這兩個問題。執行緒有時被稱為輕權程序(lightweight process),因為執行緒比程序“輕權”,一般來說,建立一個執行緒要比建立一個程序快10~100倍。

一個程序中的所有執行緒共享相同的全域性記憶體,這使得執行緒很容易共享資訊,但是這種簡易性也帶來了同步問題。

一個程序中的所有執行緒不僅共享全域性變數,而且共享:程序指令、大多數資料、開啟的檔案(如描述字)、訊號處理程式和訊號處置、當前工作目錄、使用者ID和組ID。但是每個執行緒有自己的執行緒ID、暫存器集合(包括程式計數器和棧指標)、棧(用於存放區域性變數和返回地址)、error、訊號掩碼、優先順序。在Linux中執行緒程式設計符合Posix.1標準,稱為Pthreads。所有的pthread函式都以pthread_開頭。以下先講述5個基本執行緒函式,在呼叫它們前均要包括pthread.h標頭檔案。然後再給出用它們編寫的一個TCP客戶/伺服器程式例子。

第一個函式:

int pthread_create (pthread_t *tid,const pthread_attr_t *attr,void *      (*func)(void *),void *arg);
 

一個程序中的每個執行緒都由一個執行緒ID(thread ID)標識,其資料型別是pthread_t(常常是unsigned int)。如果新的執行緒建立成功,其ID將通過tid指標返回。

每個執行緒都有很多屬性:優先順序、起始棧大小、是否應該是一個守護執行緒等等,當建立執行緒時,我們可通過初始化一個pthread_attr_t變數說明這些屬性以覆蓋預設值。我們通常使用預設值,在這種情況下,我們將attr引數說明為空指標。

最後,當建立一個執行緒時,我們要說明一個它將執行的函式。執行緒以呼叫該函式開始,然後或者顯式地終止(呼叫pthread_exit)或者隱式地終止(讓該函式返回)。函式的地址由func引數指定,該函式的呼叫引數是一個指標arg,如果我們需要多個呼叫引數,我們必須將它們打包成一個結構,然後將其地址當作唯一的引數傳遞給起始函式。

在func和arg的宣告中,func函式取一個通用指標(void *)引數,並返回一個通用指標(void *),這就使得我們可以傳遞一個指標(指向任何我們想要指向的東西)給執行緒,由執行緒返回一個指標(同樣指向任何我們想要指向的東西)。呼叫成功,返回0,出錯時返回正Exxx值。Pthread函式不設定errno。

第二個函式:

int pthread_join(pthread_t tid,void **status);
 

該函式等待一個執行緒終止。把執行緒和程序相比,pthread_creat類似於fork,而pthread_join類似於waitpid。我們必須要等待執行緒的tid,很可惜,我們沒有辦法等待任意一個執行緒結束。如果status指標非空,執行緒的返回值(一個指向某個物件的指標)將存放在status指向的位置。

第三個函式:

pthread_t pthread_self(void);
 

執行緒都有一個ID以在給定的程序內標識自己。執行緒ID由pthread_creat返回,我們可以pthread_self取得自己的執行緒ID。

第四個函式:

int pthread_detach(pthread_t tid);
 

執行緒或者是可匯合的(joinable)或者是脫離的(detached)。當可匯合的執行緒終止時,其執行緒ID和退出狀態將保留,直到另外一個執行緒呼叫pthread_join。脫離的執行緒則像守護程序:當它終止時,所有的資源都釋放,我們不能等待它終止。如果一個執行緒需要知道另一個執行緒什麼時候終止,最好保留第二個執行緒的可匯合性。Pthread_detach函式將指定的執行緒變為脫離的。該函式通常被想脫離自己的執行緒呼叫,如:pthread_detach (pthread_self ( ));

第五個函式:


void pthread_exit(void *status);
 

該函式終止執行緒。如果執行緒未脫離,其執行緒ID和退出狀態將一直保留到呼叫程序中的某個其他執行緒呼叫pthread_join函式。指標status不能指向區域性於呼叫執行緒的物件,因為執行緒終止時這些物件也消失。有兩種其他方法可使執行緒終止:

1. 啟動執行緒的函式(pthread_creat的第3個引數)返回。既然該函式必須說明為返回一個void指標,該返回值便是執行緒的終止狀態。

2. 如果程序的main函式返回或者任何執行緒呼叫了exit,程序將終止,執行緒將隨之終止。