Posix線程編程指南(一)
線程創(chuàng)建與取消
楊沙洲(pubb@163.net)
2001 年 10 月
一、線程創(chuàng)建
1.1 線程與進(jìn)程
相對(duì)進(jìn)程而言,線程是一個(gè)更加接近于執(zhí)行體的概念,它可以與同進(jìn)程中的其他線程共享數(shù)據(jù),但擁有自己的棧空間,擁有獨(dú)立的執(zhí)行序列。在串行程序基礎(chǔ)上引入線程和進(jìn)程是為了提高程序的并發(fā)度,從而提高程序運(yùn)行效率和響應(yīng)時(shí)間。
線程和進(jìn)程在使用上各有優(yōu)缺點(diǎn):線程執(zhí)行開銷小,但不利于資源的管理和保護(hù);而進(jìn)程正相反。同時(shí),線程適合于在SMP機(jī)器上運(yùn)行,而進(jìn)程則可以跨機(jī)器遷移。
1.2 創(chuàng)建線程
POSIX通過pthread_create()函數(shù)創(chuàng)建線程,API定義如下:
與fork()調(diào)用創(chuàng)建一個(gè)進(jìn)程的方法不同,pthread_create()創(chuàng)建的線程并不具備與主線程(即調(diào)用pthread_create()的線程)同樣的執(zhí)行序列,而是使其運(yùn)行start_routine(arg)函數(shù)。thread返回創(chuàng)建的線程ID,而attr是創(chuàng)建線程時(shí)設(shè)置的線程屬性(見下)。pthread_create()的返回值表示線程創(chuàng)建是否成功。盡管arg是void *類型的變量,但它同樣可以作為任意類型的參數(shù)傳給start_routine()函數(shù);同時(shí),start_routine()可以返回一個(gè)void *類型的返回值,而這個(gè)返回值也可以是其他類型,并由pthread_join()獲取。
1.3 線程創(chuàng)建屬性
pthread_create()中的attr參數(shù)是一個(gè)結(jié)構(gòu)指針,結(jié)構(gòu)中的元素分別對(duì)應(yīng)著新線程的運(yùn)行屬性,主要包括以下幾項(xiàng):
__detachstate,表示新線程是否與進(jìn)程中其他線程脫離同步,如果置位則新線程不能用pthread_join()來同步,且在退出時(shí)自行釋放所占用的資源。缺省為PTHREAD_CREATE_JOINABLE狀態(tài)。這個(gè)屬性也可以在線程創(chuàng)建并運(yùn)行以后用pthread_detach()來設(shè)置,而一旦設(shè)置為PTHREAD_CREATE_DETACH狀態(tài)(不論是創(chuàng)建時(shí)設(shè)置還是運(yùn)行時(shí)設(shè)置)則不能再恢復(fù)到PTHREAD_CREATE_JOINABLE狀態(tài)。
__schedpolicy,表示新線程的調(diào)度策略,主要包括SCHED_OTHER(正常、非實(shí)時(shí))、SCHED_RR(實(shí)時(shí)、輪轉(zhuǎn)法)和SCHED_FIFO(實(shí)時(shí)、先入先出)三種,缺省為SCHED_OTHER,后兩種調(diào)度策略僅對(duì)超級(jí)用戶有效。運(yùn)行時(shí)可以用過pthread_setschedparam()來改變。
__schedparam,一個(gè)struct ? sched_param結(jié)構(gòu),目前僅有一個(gè)sched_priority整型變量表示線程的運(yùn)行優(yōu)先級(jí)。這個(gè)參數(shù)僅當(dāng)調(diào)度策略為實(shí)時(shí)(即SCHED_RR或SCHED_FIFO)時(shí)才有效,并可以在運(yùn)行時(shí)通過pthread_setschedparam()函數(shù)來改變,缺省為0。
__inheritsched,有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新線程使用顯式指定調(diào)度策略和調(diào)度參數(shù)(即attr中的值),而后者表示繼承調(diào)用者線程的值。缺省為PTHREAD_EXPLICIT_SCHED。
__scope,表示線程間競(jìng)爭(zhēng)CPU的范圍,也就是說線程優(yōu)先級(jí)的有效范圍。POSIX的標(biāo)準(zhǔn)中定義了兩個(gè)值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統(tǒng)中所有線程一起競(jìng)爭(zhēng)CPU時(shí)間,后者表示僅與同進(jìn)程中的線程競(jìng)爭(zhēng)CPU。目前LinuxThreads僅實(shí)現(xiàn)了PTHREAD_SCOPE_SYSTEM一值。
pthread_attr_t結(jié)構(gòu)中還有一些值,但不使用pthread_create()來設(shè)置。
為了設(shè)置這些屬性,POSIX定義了一系列屬性設(shè)置函數(shù),包括pthread_attr_init()、pthread_attr_destroy()和與各個(gè)屬性相關(guān)的pthread_attr_get---/pthread_attr_set---函數(shù)。
1.4 線程創(chuàng)建的Linux實(shí)現(xiàn)
我們知道,Linux的線程實(shí)現(xiàn)是在核外進(jìn)行的,核內(nèi)提供的是創(chuàng)建進(jìn)程的接口do_fork()。內(nèi)核提供了兩個(gè)系統(tǒng)調(diào)用__clone()和fork(),最終都用不同的參數(shù)調(diào)用do_fork()核內(nèi)API。當(dāng)然,要想實(shí)現(xiàn)線程,沒有核心對(duì)多進(jìn)程(其實(shí)是輕量級(jí)進(jìn)程)共享數(shù)據(jù)段的支持是不行的,因此,do_fork()提供了很多參數(shù),包括CLONE_VM(共享內(nèi)存空間)、CLONE_FS(共享文件系統(tǒng)信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信號(hào)句柄表)和CLONE_PID(共享進(jìn)程ID,僅對(duì)核內(nèi)進(jìn)程,即0號(hào)進(jìn)程有效)。當(dāng)使用fork系統(tǒng)調(diào)用時(shí),內(nèi)核調(diào)用do_fork()不使用任何共享屬性,進(jìn)程擁有獨(dú)立的運(yùn)行環(huán)境,而使用pthread_create()來創(chuàng)建線程時(shí),則最終設(shè)置了所有這些屬性來調(diào)用__clone(),而這些參數(shù)又全部傳給核內(nèi)的do_fork(),從而創(chuàng)建的"進(jìn)程"擁有共享的運(yùn)行環(huán)境,只有棧是獨(dú)立的,由__clone()傳入。
Linux線程在核內(nèi)是以輕量級(jí)進(jìn)程的形式存在的,擁有獨(dú)立的進(jìn)程表項(xiàng),而所有的創(chuàng)建、同步、刪除等操作都在核外pthread庫(kù)中進(jìn)行。pthread庫(kù)使用一個(gè)管理線程(__pthread_manager(),每個(gè)進(jìn)程獨(dú)立且唯一)來管理線程的創(chuàng)建和終止,為線程分配線程ID,發(fā)送線程相關(guān)的信號(hào)(比如Cancel),而主線程(pthread_create())的調(diào)用者則通過管道將請(qǐng)求信息傳給管理線程。
二、線程取消
2.1 線程取消的定義
一般情況下,線程在其主體函數(shù)退出的時(shí)候會(huì)自動(dòng)終止,但同時(shí)也可以因?yàn)榻邮盏搅硪粋€(gè)線程發(fā)來的終止(取消)請(qǐng)求而強(qiáng)制終止。
2.2 線程取消的語義
線程取消的方法是向目標(biāo)線程發(fā)Cancel信號(hào),但如何處理Cancel信號(hào)則由目標(biāo)線程自己決定,或者忽略、或者立即終止、或者繼續(xù)運(yùn)行至Cancelation-point(取消點(diǎn)),由不同的Cancelation狀態(tài)決定。
線程接收到CANCEL信號(hào)的缺省處理(即pthread_create()創(chuàng)建線程的缺省狀態(tài))是繼續(xù)運(yùn)行至取消點(diǎn),也就是說設(shè)置一個(gè)CANCELED狀態(tài),線程繼續(xù)運(yùn)行,只有運(yùn)行至Cancelation-point的時(shí)候才會(huì)退出。
2.3 取消點(diǎn)
根據(jù)POSIX標(biāo)準(zhǔn),pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函數(shù)以及read()、write()等會(huì)引起阻塞的系統(tǒng)調(diào)用都是Cancelation-point,而其他pthread函數(shù)都不會(huì)引起Cancelation動(dòng)作。但是pthread_cancel的手冊(cè)頁聲稱,由于LinuxThread庫(kù)與C庫(kù)結(jié)合得不好,因而目前C庫(kù)函數(shù)都不是Cancelation-point;但CANCEL信號(hào)會(huì)使線程從阻塞的系統(tǒng)調(diào)用中退出,并置EINTR錯(cuò)誤碼,因此可以在需要作為Cancelation-point的系統(tǒng)調(diào)用前后調(diào)用pthread_testcancel(),從而達(dá)到POSIX標(biāo)準(zhǔn)所要求的目標(biāo),即如下代碼段:
2.4 程序設(shè)計(jì)方面的考慮
如果線程處于無限循環(huán)中,且循環(huán)體內(nèi)沒有執(zhí)行至取消點(diǎn)的必然路徑,則線程無法由外部其他線程的取消請(qǐng)求而終止。因此在這樣的循環(huán)體的必經(jīng)路徑上應(yīng)該加入pthread_testcancel()調(diào)用。
2.5 與線程取消相關(guān)的pthread函數(shù)
int pthread_cancel(pthread_t thread)
發(fā)送終止信號(hào)給thread線程,如果成功則返回0,否則為非0值。發(fā)送成功并不意味著thread會(huì)終止。
int pthread_setcancelstate(int state, int *oldstate)
設(shè)置本線程對(duì)Cancel信號(hào)的反應(yīng),state有兩種值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,分別表示收到信號(hào)后設(shè)為CANCLED狀態(tài)和忽略CANCEL信號(hào)繼續(xù)運(yùn)行;old_state如果不為NULL則存入原來的Cancel狀態(tài)以便恢復(fù)。
int pthread_setcanceltype(int type, int *oldtype)
設(shè)置本線程取消動(dòng)作的執(zhí)行時(shí)機(jī),type由兩種取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,僅當(dāng)Cancel狀態(tài)為Enable時(shí)有效,分別表示收到信號(hào)后繼續(xù)運(yùn)行至下一個(gè)取消點(diǎn)再退出和立即執(zhí)行取消動(dòng)作(退出);oldtype如果不為NULL則存入運(yùn)來的取消動(dòng)作類型值。
void pthread_testcancel(void)
檢查本線程是否處于Canceld狀態(tài),如果是,則進(jìn)行取消動(dòng)作,否則直接返回。
線程私有數(shù)據(jù)
本文是第二篇將向您講述線程的私有數(shù)據(jù)。
一.概念及作用
在單線程程序中,我們經(jīng)常要用到"全局變量"以實(shí)現(xiàn)多個(gè)函數(shù)間共享數(shù)據(jù)。在多線程環(huán)境下,由于數(shù)據(jù)空間是共享的,因此全局變量也為所有線程所共有。但有時(shí)應(yīng)用程序設(shè)計(jì)中有必要提供線程私有的全局變量,僅在某個(gè)線程中有效,但卻可以跨多個(gè)函數(shù)訪問,比如程序可能需要每個(gè)線程維護(hù)一個(gè)鏈表,而使用相同的函數(shù)操作,最簡(jiǎn)單的辦法就是使用同名而不同變量地址的線程相關(guān)數(shù)據(jù)結(jié)構(gòu)。這樣的數(shù)據(jù)結(jié)構(gòu)可以由Posix線程庫(kù)維護(hù),稱為線程私有數(shù)據(jù)(Thread-specific Data,或TSD)。
二.創(chuàng)建和注銷
Posix定義了兩個(gè)API分別用來創(chuàng)建和注銷TSD:
int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))
該函數(shù)從TSD池中分配一項(xiàng),將其值賦給key供以后訪問使用。如果destr_function不為空,在線程退出(pthread_exit())時(shí)將以key所關(guān)聯(lián)的數(shù)據(jù)為參數(shù)調(diào)用destr_function(),以釋放分配的緩沖區(qū)。
不論哪個(gè)線程調(diào)用pthread_key_create(),所創(chuàng)建的key都是所有線程可訪問的,但各個(gè)線程可根據(jù)自己的需要往key中填入不同的值,這就相當(dāng)于提供了一個(gè)同名而不同值的全局變量。在LinuxThreads的實(shí)現(xiàn)中,TSD池用一個(gè)結(jié)構(gòu)數(shù)組表示:
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };
創(chuàng)建一個(gè)TSD就相當(dāng)于將結(jié)構(gòu)數(shù)組中的某一項(xiàng)設(shè)置為"in_use",并將其索引返回給*key,然后設(shè)置destructor函數(shù)為destr_function。
注銷一個(gè)TSD采用如下API:
int pthread_key_delete(pthread_key_t key)
這個(gè)函數(shù)并不檢查當(dāng)前是否有線程正使用該TSD,也不會(huì)調(diào)用清理函數(shù)(destr_function),而只是將TSD釋放以供下一次調(diào)用pthread_key_create()使用。在LinuxThreads中,它還會(huì)將與之相關(guān)的線程數(shù)據(jù)項(xiàng)設(shè)為NULL(見"訪問")。
三.訪問
TSD的讀寫都通過專門的Posix Thread函數(shù)進(jìn)行,其API定義如下:
int??pthread_setspecific(pthread_key_t??key,??const???void??*pointer)
void?*?pthread_getspecific(pthread_key_t?key)
寫入(pthread_setspecific())時(shí),將pointer的值(不是所指的內(nèi)容)與key相關(guān)聯(lián),而相應(yīng)的讀出函數(shù)則將與key相關(guān)聯(lián)的數(shù)據(jù)讀出來。數(shù)據(jù)類型都設(shè)為void *,因此可以指向任何類型的數(shù)據(jù)。
在LinuxThreads中,使用了一個(gè)位于線程描述結(jié)構(gòu)(_pthread_descr_struct)中的二維void *指針數(shù)組來存放與key關(guān)聯(lián)的數(shù)據(jù),數(shù)組大小由以下幾個(gè)宏來說明:
#define?PTHREAD_KEY_2NDLEVEL_SIZE???????32
#define?PTHREAD_KEY_1STLEVEL_SIZE???\
((PTHREAD_KEYS_MAX?+?PTHREAD_KEY_2NDLEVEL_SIZE?-?1)
/?PTHREAD_KEY_2NDLEVEL_SIZE)
其中在/usr/include/bits/local_lim.h中定義了PTHREAD_KEYS_MAX為1024,因此一維數(shù)組大小為32。而具體存放的位置由key值經(jīng)過以下計(jì)算得到:
idx1st?=?key?/?PTHREAD_KEY_2NDLEVEL_SIZE
idx2nd?=?key?%?PTHREAD_KEY_2NDLEVEL_SIZE
也就是說,數(shù)據(jù)存放與一個(gè)32×32的稀疏矩陣中。同樣,訪問的時(shí)候也由key值經(jīng)過類似計(jì)算得到數(shù)據(jù)所在位置索引,再取出其中內(nèi)容返回。
四.使用范例
以下這個(gè)例子沒有什么實(shí)際意義,只是說明如何使用,以及能夠使用這一機(jī)制達(dá)到存儲(chǔ)線程私有數(shù)據(jù)的目的。
#include?
#include?
pthread_key_t???key;
void?echomsg(int?t)
{
printf("destructor?excuted?in?thread?%d,param=%d\n",pthread_self(),t);
}
void?*?child1(void?*arg)
{
int?tid=pthread_self();
printf("thread?%d?enter\n",tid);
pthread_setspecific(key,(void?*)tid);
sleep(2);
printf("thread?%d?returns?%d\n",tid,pthread_getspecific(key));
sleep(5);
}
void?*?child2(void?*arg)
{
int?tid=pthread_self();
printf("thread?%d?enter\n",tid);
pthread_setspecific(key,(void?*)tid);
sleep(1);
printf("thread?%d?returns?%d\n",tid,pthread_getspecific(key));
sleep(5);
}
int?main(void)
{
int?tid1,tid2;
printf("hello\n");
pthread_key_create(&key,echomsg);
pthread_create(&tid1,NULL,child1,NULL);
pthread_create(&tid2,NULL,child2,NULL);
sleep(10);
pthread_key_delete(key);
printf("main?thread?exit\n");
return?0;
}
給例程創(chuàng)建兩個(gè)線程分別設(shè)置同一個(gè)線程私有數(shù)據(jù)為自己的線程ID,為了檢驗(yàn)其私有性,程序錯(cuò)開了兩個(gè)線程私有數(shù)據(jù)的寫入和讀出的時(shí)間,從程序運(yùn)行結(jié)果可以看出,兩個(gè)線程對(duì)TSD的修改互不干擾。同時(shí),當(dāng)線程退出時(shí),清理函數(shù)會(huì)自動(dòng)執(zhí)行,參數(shù)為tid。
Linux
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。