《ARM Linux嵌入式系統(tǒng)開(kāi)發(fā)基礎(chǔ)》課件第4章_第1頁(yè)
《ARM Linux嵌入式系統(tǒng)開(kāi)發(fā)基礎(chǔ)》課件第4章_第2頁(yè)
《ARM Linux嵌入式系統(tǒng)開(kāi)發(fā)基礎(chǔ)》課件第4章_第3頁(yè)
《ARM Linux嵌入式系統(tǒng)開(kāi)發(fā)基礎(chǔ)》課件第4章_第4頁(yè)
《ARM Linux嵌入式系統(tǒng)開(kāi)發(fā)基礎(chǔ)》課件第4章_第5頁(yè)
已閱讀5頁(yè),還剩106頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第4章LinuxC編程基礎(chǔ)

4.1Linux下的C語(yǔ)言編程——線程操作4.2Linux下的進(jìn)程控制4.3多線程編程入門

進(jìn)程的概念是針對(duì)系統(tǒng)而不是針對(duì)用戶的,對(duì)用戶來(lái)說(shuō),面對(duì)的是程序。當(dāng)用戶執(zhí)行程序時(shí),對(duì)系統(tǒng)而言,它將啟動(dòng)一個(gè)進(jìn)程。但和程序不同的是,在進(jìn)程中,系統(tǒng)可能需要再啟動(dòng)一個(gè)或多個(gè)進(jìn)程來(lái)獨(dú)立完成的多個(gè)任務(wù)。多進(jìn)程編程的主要內(nèi)容包括進(jìn)程控制和進(jìn)程間通信。4.1Linux下的C語(yǔ)言編程——線程操作

Linux下的一個(gè)進(jìn)程在內(nèi)存里包括三部分的數(shù)據(jù),即“代碼段”、“堆棧段”和“數(shù)據(jù)段”。一般的CPU都有上述三種段寄存器,以方便操作系統(tǒng)的運(yùn)行。這三個(gè)部分也是構(gòu)成完整的執(zhí)行序列的必要部分。

“代碼段”,顧名思義,就是存放了程序代碼的數(shù)據(jù)單元,假如機(jī)器中有數(shù)個(gè)進(jìn)程運(yùn)行相同的一個(gè)程序,那么就可以使用相同的代碼段?!岸褩6巍贝娣诺木褪亲映绦虻姆祷氐刂?、子程序的參數(shù)以及程序的局部變量。而“數(shù)據(jù)段”則存放程序的全局變量、常數(shù)以及動(dòng)態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間(比如用malloc函數(shù)取得的內(nèi)存空間)。系統(tǒng)如果同時(shí)運(yùn)行數(shù)個(gè)相同的程序,則不能使用同一個(gè)堆棧段和數(shù)據(jù)段。

4.2Linux下的進(jìn)程控制

在Linux環(huán)境下,有兩個(gè)基本的操作用于創(chuàng)建和修改進(jìn)程:函數(shù)fork()用來(lái)創(chuàng)建一個(gè)新的進(jìn)程,該進(jìn)程幾乎是當(dāng)前進(jìn)程的一個(gè)完全拷貝;函數(shù)族e(cuò)xec()用來(lái)啟動(dòng)另外的進(jìn)程以取代當(dāng)前運(yùn)行的進(jìn)程。Linux的進(jìn)程控制和傳統(tǒng)的UNIX進(jìn)程控制基本一致,只在一些細(xì)節(jié)的地方有些區(qū)別,例如在Linux系統(tǒng)中調(diào)用vfork和fork完全相同。4.2.1fork()函數(shù)

fork在英文中是“分叉”的意思。一個(gè)進(jìn)程在運(yùn)行中,如果使用了fork()函數(shù),將產(chǎn)生另一個(gè)進(jìn)程,于是進(jìn)程就產(chǎn)生“分叉”了。下面具體描述如何使用fork()函數(shù),并通過(guò)程序演示使用fork()函數(shù)的基本框架:voidmain(){

inti;

if(fork()==0){

/*子進(jìn)程程序*/

for(i=1;i<1000;i++)printf("Thisischildprocess\n");

}

else{

/*父進(jìn)程程序*/for(i=1;i<1000;i++)printf("Thisisprocessprocess\n");

}

}程序運(yùn)行后,屏幕上就會(huì)交替出現(xiàn)子進(jìn)程與父進(jìn)程各打印出的一千條信息。如果程序還在運(yùn)行中,用ps命令就能看到系統(tǒng)中有兩個(gè)進(jìn)程在運(yùn)行。

調(diào)用fork()函數(shù)時(shí),fork()函數(shù)啟動(dòng)一個(gè)新的進(jìn)程,此進(jìn)程幾乎是當(dāng)前進(jìn)程的一個(gè)拷貝。子進(jìn)程和父進(jìn)程使用相同的代碼段;子進(jìn)程復(fù)制父進(jìn)程的堆棧段和數(shù)據(jù)段。父進(jìn)程的所有數(shù)據(jù)都可以留給子進(jìn)程,但是,子進(jìn)程一旦開(kāi)始運(yùn)行,雖然繼承了父進(jìn)程的一切數(shù)據(jù),但實(shí)際上數(shù)據(jù)已經(jīng)分開(kāi),相互之間不再影響,也就是說(shuō),不再共享任何數(shù)據(jù)。再要交互信息時(shí),只有通過(guò)進(jìn)程間的通信來(lái)實(shí)現(xiàn)。既然如此相像,系統(tǒng)如何來(lái)區(qū)分呢?這是由函數(shù)的返回值來(lái)決定的。對(duì)于父進(jìn)程,fork()函數(shù)返回了子程序的進(jìn)程號(hào);而對(duì)于子程序,fork()函數(shù)則返回零。在操作系統(tǒng)中,用ps函數(shù)就可以看到不同的進(jìn)程號(hào),對(duì)父進(jìn)程而言,進(jìn)程號(hào)是由比它更低層的系統(tǒng)調(diào)用賦予的;而對(duì)于子進(jìn)程而言,它的進(jìn)程號(hào)即是fork()函數(shù)對(duì)父進(jìn)程的返回值。在程序設(shè)計(jì)中,父進(jìn)程和子進(jìn)程都要調(diào)用fork()函數(shù)下的代碼,也就是利用fork()函數(shù)對(duì)父、子進(jìn)程的不同返回值用if…else…語(yǔ)句來(lái)實(shí)現(xiàn)讓父、子進(jìn)程完成不同的功能,上例在執(zhí)行時(shí)兩條信息是交互無(wú)規(guī)則地打印,這是父、子進(jìn)程獨(dú)立執(zhí)行的結(jié)果,雖然代碼似乎和串行的代碼沒(méi)有什么區(qū)別。大程序在運(yùn)行時(shí),數(shù)據(jù)段和堆棧都很大,執(zhí)行一次fork()函數(shù)就要復(fù)制一次,那么fork()函數(shù)的系統(tǒng)開(kāi)銷如何解決?一般CPU都是以“頁(yè)”為單位來(lái)分配內(nèi)存空間的,每一個(gè)頁(yè)都是實(shí)際物理內(nèi)存的一個(gè)映像,像Intel的CPU,每頁(yè)通常情況下為4086字節(jié),而無(wú)論是數(shù)據(jù)段還是堆棧段都是由許多“頁(yè)”構(gòu)成的,fork()函數(shù)復(fù)制這兩個(gè)段,只是“邏輯”上,并非“物理”上的,也就是說(shuō),實(shí)際執(zhí)行fork()函數(shù)時(shí),物理空間上兩個(gè)進(jìn)程的數(shù)據(jù)段和堆棧段都還是共享著的,當(dāng)有一個(gè)進(jìn)程寫了某個(gè)數(shù)據(jù)時(shí),這時(shí)兩個(gè)進(jìn)程之間的數(shù)據(jù)才有了區(qū)別,系統(tǒng)就將有區(qū)別“頁(yè)”從物理上也分開(kāi),于是系統(tǒng)在空間上的開(kāi)銷就可以達(dá)到最小。下面為一個(gè)耗盡機(jī)器資源的Linux小程序,其源代碼非常簡(jiǎn)單:

?voidmain()

{

for(;;)fork();

}

這個(gè)程序死循環(huán)地執(zhí)行fork()函數(shù),其結(jié)果是程序不斷產(chǎn)生進(jìn)程,而這些進(jìn)程又不斷產(chǎn)生新的進(jìn)程,系統(tǒng)資源就被這么多不斷產(chǎn)生的進(jìn)程耗盡。當(dāng)然只要系統(tǒng)管理員預(yù)先給每個(gè)用戶設(shè)置可運(yùn)行的最大進(jìn)程數(shù),惡意的程序就可被制止。4.2.2exec()函數(shù)族

在Linux中要使用exec()函數(shù)族。系統(tǒng)調(diào)用execve()函數(shù)可對(duì)當(dāng)前進(jìn)程進(jìn)行替換,替換者為指定的程序,其參數(shù)包括文件名(Filename)、參數(shù)列表(Argv)以及環(huán)境變量(Envp)。exec()函數(shù)族當(dāng)然不止一個(gè),在Linux中,分別有execl、execlp、execle、execv,execve和execvp,下面以execlp為例說(shuō)明,可以通過(guò)manexec命令來(lái)了解其他函數(shù)。

進(jìn)程一旦調(diào)用exec()函數(shù)族,它本身就“死亡”了,系統(tǒng)把代碼段替換成新程序的代碼,廢棄原有的數(shù)據(jù)段和堆棧段,并為新程序分配新的數(shù)據(jù)段與堆棧段,唯一留下的,就是進(jìn)程號(hào)。也就是說(shuō),對(duì)系統(tǒng)而言,還是同一個(gè)進(jìn)程,不過(guò)已經(jīng)是另一個(gè)程序了。

如果要啟動(dòng)另一程序但仍繼續(xù)運(yùn)行原程序的話,結(jié)合fork()函數(shù)與execlp()函數(shù)的使用,下面代碼顯示如何啟動(dòng)運(yùn)行其他程序:charcommand[256];

voidmain()

{

intrtn;/*子進(jìn)程的返回?cái)?shù)值*/

while(1){

/*從終端讀取要執(zhí)行的命令*/

printf(">");

fgets(command,256,stdin);

command[strlen(command)-1]=0;

if(fork()==0){

/*子進(jìn)程執(zhí)行此命令*/

execlp(command,command);

/*如果execlp()函數(shù)返回,表明沒(méi)有正常執(zhí)行命令,打印錯(cuò)誤信息*/

perror(command);

exit(errorno);

}

else{

/*父進(jìn)程,等待子進(jìn)程結(jié)束,并打印子進(jìn)程的返回值*/

wait(&rtn);

printf(“childprocessreturn%d\n”,.rtn);

}

}

}

此程序從終端讀入命令并執(zhí)行,執(zhí)行完成后,父進(jìn)程繼續(xù)等待從終端讀入命令。熟悉DOS和Windows系統(tǒng)調(diào)用的人一定知道DOS/Windows也有exec()類函數(shù),其使用方法是類似的,但DOS/Windows還有spawn類函數(shù),因?yàn)镈OS是單任務(wù)系統(tǒng),只能將“父進(jìn)程”駐留在機(jī)器內(nèi)再執(zhí)行“子進(jìn)程”,這就是spawn類函數(shù)。Win?32是多任務(wù)系統(tǒng),但還是保留了spawn類函數(shù)。Win32中實(shí)現(xiàn)spawn函數(shù)的方法同前述UNIX中的方法差不多,開(kāi)設(shè)子進(jìn)程后父進(jìn)程等待子進(jìn)程結(jié)束后繼續(xù)運(yùn)行。Linux是多任務(wù)的系統(tǒng),所以從核心角度上講不需要spawn類函數(shù)。在這一節(jié)里,要提到system()函數(shù)和popen()函數(shù)。system()函數(shù)先調(diào)用fork()函數(shù),然后再調(diào)用exec()函數(shù)來(lái)執(zhí)行用戶的登錄shell,通過(guò)shell來(lái)查找可執(zhí)行文件的命令并分析參數(shù),最后使用wait()族函數(shù)來(lái)等待子進(jìn)程的結(jié)束。popen()函數(shù)和system()函數(shù)相似,不同的是popen()函數(shù)調(diào)用pipe()函數(shù)創(chuàng)建一個(gè)管道,通過(guò)此管通來(lái)完成程序的標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出。

4.3多線程編程入門下面為一個(gè)典型的多線程程序代碼。

#include<stdio.h>

#include<pthread.h>/*多線程編程中必須包含這個(gè)頭文件*/

voidhandle(void)

{

inti;

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

{printf(“mynameissub_thread.\n”);

}

}

intmain(void){

pthread_tid;/*定義線程描述符號(hào)*/

inti;

intret;

ret=pthread_create(&id,NULL,(void*)thread,NULL);/*創(chuàng)建一個(gè)線程*/

if(ret!=0)

{

printf("Createpthreaderror!\n");

exit(1);

}

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

{

printf("mynameismain_thread.\n");

}

pthread_join(id,NULL);/*主線程等待之線程結(jié)束*/

return(0);

}編譯此程序,運(yùn)行mult_thread.c程序,得到如下結(jié)果:

mynameismain_thread

mynameissub_thread

mynameismain_thread

mynameissub_thread

mynameismain_thread

mynameismain_thread

mynameissub_thead

mynameismain_thread

mynameissub_thread

mpnameissub_thread

注意該程序每次運(yùn)行的結(jié)果可能會(huì)有所不同。

4.3.1創(chuàng)建線程

相對(duì)進(jìn)程而言,線程是更加接近于執(zhí)行體的概念,可以與同進(jìn)程中的其他線程共享數(shù)據(jù),并且擁有自己的??臻g以及獨(dú)立的執(zhí)行序列。在串行程序的基礎(chǔ)上引入線程和進(jìn)程是為了提高程序的并發(fā)度,從而提高其運(yùn)行效率和響應(yīng)時(shí)間。線程和進(jìn)程在使用上各有優(yōu)缺點(diǎn),線程執(zhí)行開(kāi)銷小,但不利于資源的管理和保護(hù);而進(jìn)程則相反。另外,線程適合在SMP計(jì)算機(jī)上運(yùn)行,而進(jìn)程則可以跨計(jì)算機(jī)遷移。

1.線程的創(chuàng)建

在前面的程序示例中,使用了pthread_create()函數(shù)和pthread_join()函數(shù),并聲明了一個(gè)pthread_t型變量。pthread_t是在頭文件/usr/include//bits/pthreadtypes.h中定義的,原型如下:

typedefunsignedlongintpthread_t;

是線程的標(biāo)識(shí)符。

pthread_create()函數(shù)用于創(chuàng)建新線程,并將其標(biāo)識(shí)符放入?yún)?shù)pthread指向的新線程中。函數(shù)接口和原型為

#include<pthread.h>

intpthread_creat(pthread_t*thread,pthread_attr_t*attr,void*(*start_routine)(void*),void*arg)

externintpthread_create_p(pthread_t*_thread,_constpthread_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í)候也可以不需要參數(shù),將最后一個(gè)參數(shù)設(shè)為空指針。第二個(gè)參數(shù)也可設(shè)為空指針,這樣將生成默認(rèn)屬性的線程。成功創(chuàng)建線程后,函數(shù)返回0,若不為0,則說(shuō)明創(chuàng)建線程失敗,常見(jiàn)的錯(cuò)誤返回代碼為EAGAIN和EINVAL。前者表示系統(tǒng)限制創(chuàng)建新的線程,如線程數(shù)目過(guò)多;后者表示第二個(gè)參數(shù)代表的線程屬性值非法。新創(chuàng)建的線程運(yùn)行第三參數(shù)和第四參數(shù)確定的函數(shù),原來(lái)的線程則繼續(xù)運(yùn)行下一行代碼。與fork()函數(shù)調(diào)用創(chuàng)建進(jìn)程的方法不同,pthread_create()函數(shù)創(chuàng)建的線程并不具備與主線程(即調(diào)用pthread_create()函數(shù)的線程)同樣的執(zhí)行序列,而是使其運(yùn)行為start_routine(arg)。thread返回創(chuàng)建的線程ID,而attr是創(chuàng)建線程時(shí)設(shè)置的線程屬性。pthread_create()函數(shù)的返回值表示線程創(chuàng)建是否成功。盡管arg是void類型變量,但它同樣可以作為任意類型的參數(shù)傳給start_routine()函數(shù);同時(shí),start_routine()函數(shù)可以返回一個(gè)void類型的值,且該值也可以是其他類型,由pthread_join()函數(shù)獲取。

pthread_create()函數(shù)中的attr參數(shù)是結(jié)構(gòu)指針,結(jié)構(gòu)中的元素分別對(duì)應(yīng)著新線程的運(yùn)行屬性,主要包括以下幾項(xiàng)。

(1)?_detachstate:新線程是否與進(jìn)程中的其他線程脫離同步,如果置位,則新線程不能與pthread_join同步,且退出時(shí)自行釋放所占用的資源。該屬性默認(rèn)為PTHREAD_CREATE_JOINABLE狀態(tài)。該屬性也可以在線程創(chuàng)建并運(yùn)行后通過(guò)pthead_detach()函數(shù)來(lái)設(shè)置,但只要設(shè)置為PTHREAD_CREATE_DETACH狀態(tài)(不論是創(chuàng)建時(shí)設(shè)置還是運(yùn)行時(shí)設(shè)置的),就不能再恢復(fù)到PTHREAD_CREATE_JOINABLE狀態(tài)。

(2)?_schedpolicy:新線程的調(diào)度策略,主要包括SCHED_OTHER(正常、非實(shí)時(shí))、SCHED_RR(實(shí)時(shí)、輪轉(zhuǎn)法)和SCHED_FIFO(實(shí)時(shí)、先入先出)三種,默認(rèn)為SCHED_OTHER,后兩種策略僅對(duì)超級(jí)用戶有效。該屬性運(yùn)行時(shí)可以用pthread_setschedparam()函數(shù)來(lái)改變。

(3)?_scheddparam:是一個(gè)sched_param結(jié)構(gòu),目前僅有一個(gè)sched_priority整型變量表示線程的運(yùn)行優(yōu)先級(jí)。該參數(shù)僅當(dāng)調(diào)度策略為實(shí)時(shí)間(即SCHED_RR或SCHED_FIFO)時(shí)才有效,并可以在運(yùn)行時(shí)通過(guò)pthread_setschedparam()改變,默認(rèn)為0。

(4)?_inheritsched:有兩種值可供選擇,即PTHREAD_EXPLICIT_SCHED和PTHREAD_

INHERIT_SCHED。前者表示新線程使用顯式指定調(diào)度策略和調(diào)度參數(shù)(即attr中的值),后者表示繼承調(diào)用者線程的值。

(5)?_scope:線程間競(jìng)爭(zhēng)CPU的范圍,也就是線程優(yōu)先級(jí)的有效范圍。POSIX的標(biāo)準(zhǔn)中定義了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。Thread_attr_t結(jié)構(gòu)中還有一些值不使用pthread_create()函數(shù)設(shè)置。為設(shè)置這些屬性,POSIX定義了一系列屬性設(shè)置函數(shù),包括pthread_attr_init()、pthread_

attr_destroy()和與各個(gè)屬性相關(guān)的pthread_attr_get()和pthread_attr_set()函數(shù)。

2.在Linux中創(chuàng)建線程

在Linux中創(chuàng)建線程是在內(nèi)核外進(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)線程,沒(méi)有內(nèi)核對(duì)多進(jìn)程(其實(shí)是輕量級(jí)進(jìn)程)共享數(shù)據(jù)段的支持是不行的,因此,do_fork()函數(shù)提供了很多參數(shù),如表4-1所示。表4-1do_fork()函數(shù)

參數(shù)參數(shù)說(shuō)明CLONE_VM共享內(nèi)存空間CLONE_FS共享文件系統(tǒng)信息CLONE_FILE共享文件描述符表CLONE_SIGHAND共享信號(hào)句柄表CLONE_PID共享進(jìn)程ID,僅對(duì)核內(nèi)進(jìn)程(即進(jìn)程0)有效當(dāng)使用fork系統(tǒng)調(diào)用時(shí),內(nèi)核調(diào)用do_fork()函數(shù)不使用任何共享屬性,進(jìn)程擁有獨(dú)立的運(yùn)行環(huán)境;而使用pthread_create()函數(shù)創(chuàng)建線程時(shí),設(shè)置所有屬性來(lái)調(diào)用_clone(),這些參數(shù)全部傳遞給內(nèi)核的de_fork()函數(shù),從而創(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()函數(shù),每個(gè)進(jìn)程都獨(dú)立且唯一)來(lái)管理線程的創(chuàng)建和終止,為線程分配ID,發(fā)送相關(guān)的信號(hào)(比如cancel),而主線程(pthread_create()函數(shù))的調(diào)用者則通過(guò)管道將請(qǐng)求信息傳給管理線程。4.3.2pthread_join()和pthread_exit()函數(shù)

pthread_join()函數(shù)用于等待線程的結(jié)束,也就是該函數(shù)會(huì)掛起當(dāng)前線程直到由參數(shù)比指定的線程被終止。

pthread_join()函數(shù)接口和原型為

#include<pthread.h>

intpthread_join(pthread_tth,void**thread_return);

externintpthread_join_p(pthread_t_th,void**_thread_return));第一個(gè)參數(shù)為被等待的線程標(biāo)識(shí)符;第二個(gè)參數(shù)為用戶定義的指針,可以存儲(chǔ)所等待線程的返回值。該函數(shù)是一個(gè)線程阻塞的函數(shù),調(diào)用它的函數(shù)將一直等待到所等待的線程結(jié)束為止,當(dāng)函數(shù)返回時(shí),所等待線程的資源會(huì)被收回。結(jié)束線程有兩種途徑,一種是當(dāng)函數(shù)結(jié)束時(shí),調(diào)用它的線程結(jié)束;另一種是通過(guò)pthread_exit()函數(shù)退出線程。

pthread_exit()函數(shù)接口和原型為

#include<pthread.h>

intpthread_exit(void*retval);

externvoidpthread_exit_p(void*_retval))_attribute_((_noreturn_));該函數(shù)唯一的參數(shù)是返回代碼,只要pthread_join中的第二個(gè)參數(shù)thread_return不是NULL,那么這個(gè)值就會(huì)傳遞給thread_return。一個(gè)線程不能被多個(gè)線程等待,否則當(dāng)?shù)谝粋€(gè)接收到信號(hào)的線程成功返回時(shí),其余調(diào)用pthread_join的線程則返回錯(cuò)誤代碼ESRCH。

根據(jù)以上內(nèi)容完成的一個(gè)簡(jiǎn)單程序如下:#ifdef_Linux_

#define_REENTRANT

#define_POSIX_SOURCE

#endif/*HackforLinuxThreads*/

#ifdef_Linux_

#define_P_P

#endif

#include<pthread.h>

#include<string.h> /*forstrerror()*/

#include<stdio.h>#defineNTHREADS4

#defineerrexit(code,str)

fprintf(stderr,"%s:%s\n",(str),strerror(code));\

exit(1);

/********thisisthethreadcode*/

void*hola(void*arg)

{

intmyid=*(int*)arg;

printf("Hello,world,I'm%d\n",myid);

returnarg;

}

/********thisisthemainthread‘scode*/

intmain(intargc,char*argv[])

{

intworker;

pthread_tthreads[NTHREADS];

/*holdsthreadinfo*/

intids[NTHREADS]; /*holdsthreadargs*/

interrcode;

/*holdspthreaderrorcode*/

int*status; /*holdsreturncode*/

/*createthethreads*/

for(worker=0;worker<NTHREADS;worker++){ids[worker]=worker;

if(errcode=pthread_create(&threads[worker],/*threadstruct

*/

NULL,/*defaultthreadattributes*/

hola,/*startroutine */

&ids[worker])){/*argtoroutine */

errexit(errcode,"pthread_create");

}

}

/*reapthethreadsastheyexit*/

for(worker=0;worker<NTHREADS;worker++){

/*waitforthreadtoterminate*/

if(errcode=pthread_join(threads[worker],(void*)&status)){

errexit(errcode,"pthread_join");

}

/*checkthread'sexitstatusandreleaseitsresources*/

if(*status!=worker){

fprintf(stderr,"thread%dterminatedabnormally\n",worker);

exit(1);

}

}

return(0);

}4.3.3取消線程

線程在其主體函數(shù)退出的時(shí)候會(huì)自動(dòng)終止,也可以因?yàn)榻邮盏搅硪粋€(gè)線程發(fā)來(lái)的終止(取消)請(qǐng)求而強(qiáng)制終止。1.線程取消的方法

線程取消的方法是向目標(biāo)線程發(fā)送cancel信號(hào),但如何處理該信號(hào)則由目標(biāo)線程決定,即無(wú)論忽略、立即終止或者繼續(xù)運(yùn)行至cancelation-point(取消點(diǎn)),都取決于不同的cancelation狀態(tài)。線程接收到cancel信號(hào)的默認(rèn)處理(即pthread_create()創(chuàng)建線程的默認(rèn)狀態(tài))后將繼續(xù)運(yùn)行至取消點(diǎn),即設(shè)置canceled狀態(tài)后,線程繼續(xù)運(yùn)行,直到運(yùn)行至cancelation-point才會(huì)退出。

2.取消點(diǎn)

根據(jù)POSIX標(biāo)準(zhǔn),pthrad_join()、pthread_testcancel()、pthread_cond_wait()、pthread_condtimedwait()、sem_wait()、sigwait()以及read()、write()等函數(shù)都可引起阻塞的系統(tǒng)調(diào)用至cancelation-point,而其他pthread函數(shù)都不能引起cancelation動(dòng)作。但是pthread_cancel的手冊(cè)中提到,由于LinuxThread庫(kù)與C庫(kù)結(jié)合得不好,因此目前C庫(kù)函數(shù)都不是cancelation-point;但cancel信號(hào)能使線程從阻塞的系統(tǒng)調(diào)用中退出,并置EINTR錯(cuò)誤碼,所以可以在需要作為cancelation-point的系統(tǒng)調(diào)用前后調(diào)用pthread_testcancel()函數(shù),以達(dá)到POSIX標(biāo)準(zhǔn)所要求的目標(biāo),即如下代碼段:

pthread_testcancel();

retcode=read(fd,buffer,length);

pthread_testcancel();

3.線程循環(huán)

如果線程處于無(wú)限循環(huán)中,且循環(huán)體內(nèi)沒(méi)有執(zhí)行至取消點(diǎn)路徑,則線程無(wú)法由外部其他線程的取消請(qǐng)求而終止,在這種循環(huán)體的路徑上應(yīng)該加入pthread_testcancel()函數(shù)調(diào)用。

4.與取消線程相關(guān)的pthread函數(shù)

與取消線程相關(guān)的pthread函數(shù)代碼如下:

#include<pthread.h>

intpthread_cancel(pthread_tthread)

intpthread_setcancelstate(intstate,int*oldstate)

intpthread_setcanceltype(inttype,int*oldstate)

voidpthread_testcancel(void)當(dāng)前線程中通過(guò)調(diào)用pthread_cancel()函數(shù)可以取消另一個(gè)線程,然后會(huì)發(fā)送終止信號(hào)給thread線程,如果成功則返回0,否則返回非0值。發(fā)送成功并不意味著thread終止。

intpthread_setcancelstate(intstate,int*oldstate)用于設(shè)置本線程對(duì)cancel信號(hào)的反應(yīng),state有兩種取值:PTHREAD

_CANCELENABLE(默認(rèn))和PTHREAD_CANCEL

_DISABLE,分別表示收到信號(hào)后設(shè)為canceled狀態(tài)和忽略cancel信號(hào)繼續(xù)運(yùn)行。old_state如果不為NULL,則存入原來(lái)的cancel狀態(tài)以便恢復(fù)。

intpthread_setcanceltype(inttype,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,則存入原來(lái)的取消動(dòng)作類型值。

voidpthread_testcancel(void)用于檢查本線程是否處于canceled狀態(tài)。如果是canceled狀態(tài),則執(zhí)行取消動(dòng)作,否則直接返回。4.3.4線程私有數(shù)據(jù)

在單線程程序中,經(jīng)常要用全局變量實(shí)現(xiàn)多個(gè)函數(shù)共享數(shù)據(jù)。在多線程環(huán)境下,由于數(shù)據(jù)空間是共享的,因此全局變量也被各線程共有。在應(yīng)用程序設(shè)計(jì)過(guò)程中有必要提供線程私有的全局變量,僅在某個(gè)線程中有效。但可以跨多個(gè)函數(shù)進(jìn)行訪問(wèn),比如程序可能需要每個(gè)線程維護(hù)一個(gè)鏈表,要使用相同的函數(shù)操作,最簡(jiǎn)單的辦法就是使用同名而不同變量地址的線程相關(guān)數(shù)據(jù)結(jié)構(gòu)。這樣的數(shù)據(jù)結(jié)構(gòu)可以由POSIX線程庫(kù)維護(hù),稱為線程私有數(shù)據(jù)(Thread-SpecificData,TSD)。

POSIX定義了兩個(gè)接口分別用于創(chuàng)建和注銷TSD:

?Intpthread_key_create(pthread_key_tkey,void(*destr_function)(void*))

該函數(shù)在TSD池中分配一項(xiàng),再將其值賦給key供以后訪問(wèn)使用。如果destr_function不為空,則當(dāng)線程退(pthread

_exit)時(shí)將以key所關(guān)聯(lián)的數(shù)據(jù)為參數(shù)調(diào)用destr_function(),以釋放分配的緩沖區(qū)。不論哪個(gè)線程調(diào)用ptbead_key_create

(),其創(chuàng)建的key都是所有線程可訪問(wèn)的,但各個(gè)線程可根據(jù)需要向key中填入不同的值,這就相當(dāng)于提供了一個(gè)同名而不同值的全局變量。在LinuxThreads的實(shí)現(xiàn)過(guò)程中,TSD池用一個(gè)結(jié)構(gòu)數(shù)組表示:

staticstructpthread_key_structpthread_keys

[PTHREAD_KEYS_MAX]={{0,NULL}};

創(chuàng)建TSD就相當(dāng)于將結(jié)構(gòu)數(shù)組中的某一項(xiàng)設(shè)置為in_use,并將其索引返回*key,然后設(shè)置destructor函數(shù)為destr_function。刪除TSD可采用如下函數(shù):

?intpthread_key_delete(pthread_key_tkey)

該函數(shù)并不檢查當(dāng)前是否有線程正在使用這個(gè)TSD,也不會(huì)調(diào)用清理函數(shù)(destr_function()),而只是釋放TSD以供下一次調(diào)用pthread_key_create()時(shí)使用。在LinuxThreads中,還可將與其相關(guān)的線程數(shù)據(jù)項(xiàng)設(shè)為NULL。訪問(wèn)TSD的讀/寫都通過(guò)專門的POSIXthread函數(shù)進(jìn)行,其函數(shù)原型如下:

intpthread_setspecific(pthread_key_tkey,constvoid*pointer)

void*pthread_getspecific(pthread_key_tkey)

寫入(pthreadet_setpecific())時(shí),將pointer的值(不是它所指的內(nèi)容)與key相關(guān)聯(lián),由相應(yīng)的讀出函數(shù)將與key相關(guān)聯(lián)的數(shù)據(jù)讀出來(lái)。數(shù)據(jù)類型都設(shè)為void,即可以指向任何類型的數(shù)據(jù)。在LinuxThreads中,使用了位于線程描述結(jié)構(gòu)(_pthread_descry_struct)中的二維void*指針數(shù)組來(lái)存放與key關(guān)聯(lián)的數(shù)據(jù),數(shù)組大小由以下宏來(lái)說(shuō)明:#definePTHREAD_KEY_2nDLEVEL_SIZE32

#definePTHREAD_KEY_1stLEVEL_SIZE\

((PTHREAD_KEYS_MAX+PTHREAD_2NDLEEVEL

_SIZE–1)

/PTHREAD_KEY_2NDLEVEL_SIZE)在/usr/include/bits/local_lim.h中定義了PTHREAD_KEY_MAX為1024,因此一維數(shù)組的大小為32。具體位置的存放可由key值經(jīng)過(guò)以下計(jì)算得到:

idxlst=key/PTHREAD_KEY_2NDLEVEL_SIZE

idx2nd=key/PTHREAD_KEY_2NDLEVEL_SIZE

即數(shù)據(jù)存放于一個(gè)32×32的稀疏矩陣中。同樣,訪問(wèn)的時(shí)候也可由key值經(jīng)過(guò)類似計(jì)算得到數(shù)據(jù)所在位置的索引,再取出其中的內(nèi)容返回。

以下程序說(shuō)明了如何利用這一機(jī)制達(dá)到存儲(chǔ)線程私有數(shù)據(jù)的目的。#include<stdio.h>

#include<pthread.h>

pthread_key_tkey;

voidechomsg(intt)

{

printf("destructorexcutedinthread%d,param=%d\n",pthread_self(),t);

}

void*child1(void*arg){

inttid=pthread_self();

printf("thread%denter\n",tid);

pthread_setspecific(key,(void*)tid);

sleep(2);

printf("thread%dreturns%d\n",tid,pthread_getspecific(key));

sleep(5);

}void*child2(void*arg)

{

inttid=pthread_self();

printf("thread%denter\n",tid);

pthread_setspecific(key,(void*)tid);

sleep(1);

printf("thread%dreturns%d\n",tid,pthread_getspecific(key));

sleep(5);

}

intmain(void)

{inttid1,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("mainthreadexit\n");

return0;

}給上述程序創(chuàng)建兩個(gè)線程,分別設(shè)置同一個(gè)線程私有數(shù)據(jù)為自己的線程ID,為了檢驗(yàn)其私有性,程序錯(cuò)開(kāi)了兩個(gè)線程私有數(shù)據(jù)的寫入和讀出時(shí)間,從程序運(yùn)行結(jié)果可以看出,兩個(gè)線程對(duì)TSD的修改互不干擾。同時(shí),當(dāng)線程退出時(shí),清理函數(shù)會(huì)自動(dòng)執(zhí)行,參數(shù)為tid。4.3.5互斥鎖

盡管在POSIXthread中同樣可以使用IPC的信號(hào)量機(jī)制來(lái)實(shí)現(xiàn)互斥鎖(Mutex)功能,但顯然semphore的功能過(guò)于強(qiáng)大,所以在POSIXthread中定義了另外一套專門用于線程同步的mutex函數(shù)。函數(shù)原型如下:#include<pthread.h>

pthread_mutex_tfastmutex=PTHREAD_MUTEX_INITIALIZER

pthread_mutex_trecmutex=PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP

pthread_mutex_errchkmutex=PTHREAD_ERRCHECK_MUTEX_INITIALIZER_NP

intpthread_mutex_init(pthread_mutex_t*attr,constpthread_mutexattr_t*mutexattr);

intpthread_mutex_lock(pthread_mutex_t*mutex);

intpthread_mutex_unlock(pthread_mutex_t*mutex);

intpthread_mutex_trylock(pthread_mutex_t*mutex);

intpthread_mutex_destroy(pthread_mutex_t*mutex);互斥鎖的工作原理比較簡(jiǎn)單,比如線程a試圖鎖定一個(gè)互斥對(duì)象,而此時(shí)線程b已鎖定了同一個(gè)互斥對(duì)象,線程a就會(huì)進(jìn)入睡眠狀態(tài)。一旦線程b釋放了互斥對(duì)象(通過(guò)pthread

_mutex_unlock()調(diào)用),那么線程a就能鎖定該互斥對(duì)象(換句話說(shuō),線程a將從pthread_mutex_lock中返回,同時(shí)互斥對(duì)象被鎖定)。同樣地,當(dāng)線程a正在鎖定互斥對(duì)象時(shí),如果線程c也試圖鎖定,那么線程c將臨時(shí)進(jìn)入睡眠狀態(tài)。對(duì)已鎖定的互斥對(duì)象,調(diào)用pthread_mutex_lock的所有線程都將進(jìn)入睡眠狀態(tài),需“排隊(duì)”訪問(wèn)該互斥對(duì)象。通常使用pthread_mutex_lock()和pthread_mutex_unlock()來(lái)保護(hù)數(shù)據(jù)結(jié)構(gòu),即通過(guò)線程的鎖定和解鎖來(lái)保護(hù)。對(duì)于某一數(shù)據(jù)結(jié)構(gòu),要確保某一時(shí)刻只有一個(gè)線程能夠訪問(wèn)??梢酝茰y(cè),當(dāng)線程試圖鎖定一個(gè)未加鎖的互斥對(duì)象時(shí),POSIX線程庫(kù)將同意鎖定,而不會(huì)使其進(jìn)入睡眠狀態(tài)。程序如下:#include<pthread.h>

#include<stdio.h>

#include<stdlib.h>

#include"pt.h" /*自定義頭文件*/

#defineNTHREADS4

/*globaldataarea*/

intmwork,nwork,pwork; /*dimensionsofmatrices */

double**mata,**matb,**matc; /*ptrstothematrices */

intmdone; /*#ofrowsalreadydone */

pthread_mutex_tmdone_mutex; /*lockformdone */

/******************************************************

*returnthenextrownumbertodoor-1ifwe'redone*/

intnextrow(){

intres;

pthread_mutex_lock(&mdone_mutex,“mutexlck”); /*lockthemutex */

res=mdone++;

/*get&incrrowcntr*/

pthread_mutex_unlock(&mdone_mutex,“mutexunlck”);

/*unlockthemutex */

return(res<mwork?res:-1);

/*return-1ifdone */

}/**********************************************************

*maincodeforeachthread--itcomputesrowsoftheproducta*b*/

pthread_addr_tthread_code(pt_arg_targ)

{

intn,p;

/*nandpcounters */

intm;

/*rowwe‘reworkingon */

doublesum;/*temporaryvar */while((m=nextrow())!=-1){ /*m=rowtodo*/

for(p=0;p<pwork;p++){/*p=coltodo,doeachcol*/

for(sum=0.0,n=0;n<nwork;n++){

sum+=mata[m][n]*matb[n][p];/*computetheelt*/

}

matc[m][p]=sum;/*saveresultinc */

}

}

return(NULL);

}/***********************************

*thisisthemainmultiplyroutine

*/

voiddomult(intm,intn,intp,

double**a,/*a:mxnmatrix */

double**b,/*b:nxpmatrix */

double**c)/*c:mxpmatrix=a*b */

{

mdone=0;mwork=m;nwork=n;pwork=p;/*initglbstate

info */

mata=a;matb=b;matc=c;pt_fork(NTHREADS,thread_code,NULL,NULL);/*runcode

inparallel(自定義函數(shù))*/

}

/***************************allocateanmxnmatrix*/

double**newmat(intm,intn)

{

double**res=(double**)malloc(m*sizeof(double*));

inti;

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

res[i]=(double*)malloc(n*sizeof(double));

return(res);}

/***************mainprogram*/

intmain(intargc,char*argv[])

{

double**a=newmat(2,2),**b=newmat(2,2),**c=newmat(2,2);

a[0][0]=1.0;a[0][1]=2.0; /*initializea */

a[1][0]=2.0;a[1][1]=1.0;

b[0][0]=1.0;b[0][1]=2.0; /*initializeb */

b[1][0]=3.0;b[1][1]=4.0;pthread_mutex_init(&mdone_mutex,"initmutex");/*initializethemutex*/

domult(2,2,2,a,b,c); /*dothemmult:ansis*/

printf("%8.4f%8.4f\n",c[0][0],c[0][1]); /*7.000010.0000 */

printf("%8.4f%8.4f\n",c[1][0],c[1][1]); /*5.00008.0000 */

pthread_mutex_destroy(&mdone_mutex,"delmutex");

return(0);

一般有兩種方法創(chuàng)建互斥鎖:靜態(tài)方式和動(dòng)態(tài)方式。POSIX定義了一個(gè)宏P(guān)THREAD_MUTEX_INITIALIZER來(lái)靜態(tài)初始化互斥鎖,方法如下:

pthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER

在LinuxThreads的實(shí)現(xiàn)過(guò)程中,pthread_mutex_t是一個(gè)結(jié)構(gòu),而PTHREAD_MUTEX_INITIALIZER則是一個(gè)結(jié)構(gòu)

常量。動(dòng)態(tài)方式是采用pthread_mutex_init()初始化互斥鎖,其API定義為

intpthread_mutex_init(pthread_mutex_t*mutex,constpthread_mutexattr_t*mutexattr)

其中,mutexattr用于指定互斥鎖的屬性,如果為NULL則使用默認(rèn)屬性。pthread_mutex_destroy()用于注銷互斥鎖,其API定義如下:

intpthread_mutex_destroy(pthread_mutex_t*mutex)

注銷互斥鎖即意味著釋放占用的資源,且要求鎖當(dāng)前處于開(kāi)放狀態(tài)。由于在Linux中,互斥鎖并不占用任何資源,因此Linuxthreads中的pthread_mutex_destroy()除了檢查鎖的狀態(tài)以外(鎖定狀態(tài)則返回EBUSY)沒(méi)有其他作用。

1.互斥鎖的屬性

互斥鎖的屬性在創(chuàng)建的時(shí)候指定,在Linux線程的實(shí)現(xiàn)過(guò)程中僅有一個(gè)鎖類型屬性,不同類型的鎖在試圖對(duì)一個(gè)已經(jīng)被鎖定的互斥鎖加鎖時(shí)表現(xiàn)不同。目前POSIX線程庫(kù)有以下四個(gè)值可供選擇:

(1)?PTHREAD_MUEX_TIMED_NP:默認(rèn)值,也就是普通鎖。當(dāng)一個(gè)線程加鎖以后,其余請(qǐng)求鎖的線程將形成一個(gè)等待隊(duì)列,并在解鎖后按優(yōu)先級(jí)獲得鎖。這種鎖策略保證了資源分配的公平性。

(2)?PTHREAD_MUEX_RECURSIVE_NP:嵌套鎖,允許同一個(gè)線程對(duì)多次成功獲得同一個(gè)鎖,并多次通過(guò)unlock解鎖。對(duì)于不同線程的請(qǐng)求,要在解鎖時(shí)重新競(jìng)爭(zhēng)。

(3)?PTHREAD_MUTEX_ERRORCHECK_NP:檢錯(cuò)鎖,如果同一個(gè)線程請(qǐng)求同一個(gè)鎖則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP的動(dòng)作相同。這樣不允許多次加鎖。

(4)?PTHREAD_MUTEX_ADAPTIVE_NP:適應(yīng)鎖,動(dòng)作最簡(jiǎn)單的鎖類型,僅等待解鎖后重新競(jìng)爭(zhēng)。

2.鎖操作

鎖操作主要包括加鎖(pthread_mutex_lock())、解鎖(pthread_mutex_unlock())和測(cè)試加鎖(pthread_mutex_trylock())三種,不論哪種類型的鎖,都不可能被兩個(gè)不同的線程同時(shí)得到,必須要等待解鎖。對(duì)于普通鎖和適應(yīng)鎖,解鎖者可以是同一進(jìn)程內(nèi)的任何線程;檢錯(cuò)鎖必須由加鎖者解鎖才有效,否則返回EPERM;對(duì)于同一進(jìn)程中的線程,如果加鎖后沒(méi)有解鎖,則任何其他線程都無(wú)法再獲得鎖。其加鎖和解鎖函數(shù)如下:

intpthread_mutex_lock(pthread_mutex_t*mutex)

intpthread_mutex_unlock(pthread_mutex_t*mutex)

intpthread_mutex_trylock(pthread_mutex_t*mutex)

pthead_mutex_trylock()的含義與pthread_mutex_lock()的類似,不同的是在鎖已經(jīng)被占據(jù)時(shí)返回EBUSY,而不是掛起等待。3.線程鎖機(jī)制

除了以上所說(shuō)的線程鎖以外,其他POSIX線程鎖機(jī)制的Linux實(shí)現(xiàn)都不是取消點(diǎn),因此,延遲取消類型的線程不會(huì)因收到取消信號(hào)而離開(kāi)加鎖等待。值得注意的是,如果線程在加鎖后解鎖前被取消,則鎖將永遠(yuǎn)保持鎖定狀態(tài),因此,如果在關(guān)鍵區(qū)段內(nèi)有取消點(diǎn)存在,或者設(shè)置了異步取消類型,那么必須在退出回調(diào)函數(shù)中解鎖。這種鎖機(jī)制也不是異步信號(hào)安全的,不應(yīng)該在信號(hào)處理過(guò)程中使用互斥鎖,否則容易造成死鎖。條件變量就是利用線程間共享的全局變量進(jìn)行同步的機(jī)制,主要包括兩個(gè)動(dòng)作:一個(gè)線程掛起,以等待“條件變量的條件成立”而掛起;另一個(gè)線程使“條件成立”(給出條件成立信號(hào))。為了防止競(jìng)爭(zhēng),條件變量的使用總是和一個(gè)互斥鎖結(jié)合。

4.條件變量

1)創(chuàng)建和注銷條件變量

創(chuàng)建和注銷條件變量與互斥鎖一樣,有靜態(tài)、動(dòng)態(tài)兩種創(chuàng)建方式。靜態(tài)方式使用PTHREAD_COND_INITIALIZER常量,即

pthread_cond_tcond=PTHREAD_COND_INITIALIZER

動(dòng)態(tài)方式調(diào)用pthread_cond_init()函數(shù),API定義如下:

intpthread_cond_init(pthread_cond_t*cond,pthread

_condattr_t*cond_attr)

盡管POSIX標(biāo)準(zhǔn)中為條件變量定義了屬性,但在LinuxThreads中沒(méi)有實(shí)現(xiàn),因此cond_attr的值通常為NULL,并被忽略。注銷一個(gè)條件變量需要調(diào)用pthread

_cond_destroy(),只有當(dāng)沒(méi)有線程在該條件變量上等待的時(shí)候才能進(jìn)行注銷,否則會(huì)返回EBUSY。因?yàn)闆](méi)有給Linux實(shí)現(xiàn)的條件變量分配資源,所以注銷動(dòng)作只包括檢查是否有等待線程。API定義如下:

intpthread_cond_destroy(pthread_cond_t*cond)

2)等待和激發(fā)條件變量

等待和激發(fā)條件變量的函數(shù)原型如下:

intpthread_cond_wait(pthread_cond_t*cond,pthread

_mutex_t*mutex)

intpthread_cond_timewait(pthread_cond_t*cond,pthread

_mutex_t*mutex,conststructtimespec*abstime)

等待條件有兩種方式:無(wú)條件等待和計(jì)時(shí)等待。

無(wú)條件等待:

pthread_cond_wait()

計(jì)時(shí)等待:

?pthread_cond_timewait()

對(duì)于計(jì)時(shí)等待方式,如果在給定時(shí)刻前條件沒(méi)有滿足,則退回ETIMEOUT,結(jié)束等待。其中abstime()和time()的系統(tǒng)調(diào)用以相同的絕對(duì)時(shí)間的形式出現(xiàn),0表示格林尼治時(shí)間——1970年1月1日0時(shí)0分0秒。無(wú)論哪種等待方式,都必須和一個(gè)互斥鎖配合,以防止多個(gè)線程同時(shí)請(qǐng)求pthread_cond_wait()的競(jìng)爭(zhēng)條件(RaceCondition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUEX_TIMED_NP)或適應(yīng)鎖(PTHREAD

_MUTEX_ADAPTIVE_NP),且在調(diào)用pthread_cond_wait()前必須由本線程加鎖(pthread_mutex_lock()),而在更新條件等待隊(duì)列前mutex保持鎖定狀態(tài),并在線程掛起進(jìn)入等待前解鎖。在條件滿足而離開(kāi)pthreac_cond_wait()前,mutex將被重新加鎖,以和進(jìn)入pthread_cond_wait()前的加鎖動(dòng)作對(duì)應(yīng)。激發(fā)條件有兩種:pthread_cond_signal()激活一個(gè)等待該條件的線程,當(dāng)存在多個(gè)等待線程時(shí),按入隊(duì)順序激活;而pthread_cond_broadcast()則激活所有的等待線程。其他pthread_cond_wait()和pthread_cond_timed_wait()都被實(shí)現(xiàn)為取消點(diǎn),因此,在該處等待的線程將立即重新運(yùn)行,在重新鎖定mutex后離開(kāi)ptnread_cond_wait(),然后執(zhí)行取消動(dòng)作。也就是說(shuō),如果pthread_cond_wait()被取消,則mutex保持鎖定狀態(tài),需要定義退出回調(diào)函數(shù)為其解鎖。

下面的示例程序中,有兩個(gè)線程被啟動(dòng),并等待同一個(gè)條件變量。如果不使用回調(diào)函數(shù),tid2將在pthread_mutex_lock()處永久等待;如果使用回調(diào)函數(shù),則tid2的條件等待以及主線程的條件激發(fā)都能正常工作。具體程序如下:

#include<stdio.h>

#include<pthread.h>

#include<unistd.h>

pthread_mutex_tmutex;

pthread_cond_tcond;

void*child1(void*arg){

pthread_cleanup_push(pthread_mutex_unlock,&mutex);/*comment1*/

while(1){

printf("thread1getrunning\n");

printf("thread1pthread_mutex_lockreturns%d\n",pthread_mutex_lock(&mutex));

pthread_cond_wait(&cond,&mutex);

printf("thread1conditionapplied\n");

pthread_mutex_unlock(&mutex);

sleep(5);

}

pthread_cleanup_pop(0);/*comment2*/}

void*child2(void*arg)

{

while(1){

sleep(3);/*comment3*/

printf("thread2getrunning.\n");

printf("thread2pthread_mutex_lockreturns%d\n“

,pthread_mutex_lock(&mutex));pthread_cond_wait(&cond,&mutex);

printf("thread2conditionapplied\n");

pthread_mutex_unlock(&mutex);

sleep(1);

}

}

intmain(void)

{

inttid1,tid2;

printf("hello,conditionvariabletest\n");

pthread_mutex_init(&mutex,NULL);

pthread_cond_init(&cond,NULL);

pthread_create(&tid1,NULL,child1,NULL);

pthread_create(&tid2,NULL,child2,NULL);do{

sleep(2); /*comment4*/

pthread_cancel(tid1); /*comment5*/

sleep(2); /*comment6*/

pthread_cond_signal(&cond);

}while(1);

sleep(100);

pthread_exit(0);

}不執(zhí)行注釋(comment)5的pthread_cancel動(dòng)作,即使沒(méi)有sleep()延時(shí)操作,child1和child2都能正常工作。注釋3和注釋4的延遲使child1有時(shí)間完成取消動(dòng)作,使child2能在child1退出后執(zhí)行請(qǐng)求鎖操作。如果沒(méi)有注釋1和注釋2的回調(diào)函數(shù)定義,則系統(tǒng)將掛起在child2請(qǐng)求鎖的地方;如果既不做注釋3也不做注釋4的延時(shí),則child2必能在child1完成取消動(dòng)作前得到控制,從而順利執(zhí)行申請(qǐng)鎖的操作,但卻可能pthread

_cond_wait()中掛起,因?yàn)槠渲幸灿猩暾?qǐng)mutex的操作。

child1函數(shù)給出的是標(biāo)準(zhǔn)的條件變量的使用方式:回調(diào)函數(shù)保護(hù),等待條件前鎖定,pthread_cond_wait()返回后解

鎖。條件變量機(jī)制不是異步信號(hào)安全的,在信號(hào)處理函數(shù)中調(diào)用pthread_cond_signal()或pthread_cond_broadcast()可能引起死鎖。4.3.6信號(hào)燈

信號(hào)燈與互斥鎖和條件變量的主要區(qū)別在于“燈”的概念,燈亮意味著資源可用,燈滅則意味著不可用。如果說(shuō)后兩種同步方式側(cè)重于“等待”操作,即資源不可用的話,那么信號(hào)燈方式則偏重于點(diǎn)燈,即告知資源可用;若沒(méi)有等待線程的解鎖或激發(fā)條件是沒(méi)有意義的,沒(méi)有等待燈亮的線程的點(diǎn)燈操作則有效,且保持燈亮狀態(tài)。信號(hào)燈除了應(yīng)用于燈亮/燈滅這種二元燈外,也可用于大于1的燈數(shù),表示資源數(shù)大于1的多元燈。1.創(chuàng)建和注銷POSIX信號(hào)燈

創(chuàng)建和注銷POSIX信號(hào)燈的標(biāo)準(zhǔn)定義了有名信號(hào)燈和無(wú)名信號(hào)燈兩種,但LinuxThreads的實(shí)現(xiàn)僅有無(wú)名燈,有名燈除了應(yīng)用于多進(jìn)程之間外,在使用上與無(wú)名燈并無(wú)區(qū)別,下面僅就無(wú)名燈進(jìn)行討論。intsem_init(sem_t*sem,intpshared,

unsignedintvalue)是創(chuàng)建信號(hào)燈的API,其中value為信號(hào)燈的初值,pshared表示是否為多進(jìn)程共享。LinuxThreads沒(méi)有實(shí)現(xiàn)多進(jìn)程共享的信號(hào)燈,因此所有非0值的pshared輸入都將使sem_init()返回-1,且置errno為ENOSYS。初始化好的信號(hào)燈由sem變量表征,用于點(diǎn)燈、滅燈操作。

Intsem_destroy(sem_t*sem)是注銷信號(hào)燈的API,被注銷的信號(hào)燈sem要求已沒(méi)有線程在等待該信號(hào)燈,否則返回-1,且置errno為EBUSY。除此之外,LinuxThreads的信號(hào)燈注銷函數(shù)不執(zhí)行其他動(dòng)作。

點(diǎn)燈和滅燈:

intsem_post(sem_t*sem)

點(diǎn)燈操作將信號(hào)燈值加1,表示增加一個(gè)可訪問(wèn)的資源。

intsem_wait(sem_t*sem)

intsem_trywait(sem_t*sem)其中,sem_wait()為等待燈亮的操作,燈亮(信號(hào)燈值大于0)后將信號(hào)燈值減1并返回。sem_trywait()為sem_wait()的非阻塞版,如果信號(hào)燈計(jì)數(shù)大于0,則值減1并返回0,否則立即返回-1,置errno為EAGAIN。

intsem_getvalue(sem_t*sem,int*sval)

該語(yǔ)句用于讀取sem中的燈計(jì)數(shù),將其存入*sval中并返回0。其他sem_wait被實(shí)現(xiàn)為取消點(diǎn),sem_post()是唯一能用于異步信號(hào)處理函數(shù)的POSIX異步信號(hào)安全的API。

2.異步信號(hào)

由于LinuxThreads是在核外使用核內(nèi)輕量級(jí)進(jìn)程實(shí)現(xiàn)的線程,因此基于內(nèi)核的異步信號(hào)操作對(duì)于線程也是有效的。但同時(shí),因?yàn)楫惒叫盘?hào)總是實(shí)際發(fā)往某個(gè)進(jìn)程,所以無(wú)法實(shí)現(xiàn)POSIX標(biāo)準(zhǔn)。所要求的“信號(hào)到達(dá)某個(gè)進(jìn)程,然后再由該進(jìn)程將信號(hào)分發(fā)到所有沒(méi)有阻塞該信號(hào)的線程中”,只能影響其中一個(gè)線程。

POSIX異步信號(hào)也是一個(gè)標(biāo)準(zhǔn)C庫(kù)提供的功能,主要包括信號(hào)集管理(sigemptyset()、sigfillset()、sigaddset()、sigdelset()、sigismember()等),信號(hào)處理函數(shù)安裝(sigaction()),信號(hào)阻塞控制(sigprocmask()),被阻塞信號(hào)查詢(sigpending())及信號(hào)等待(sigsuspend())等,它們與發(fā)送信號(hào)的kill()等函數(shù)配合就能實(shí)現(xiàn)進(jìn)程間異步信號(hào)的功能。LinuxThreads圍繞線程封裝了sigaction()和raISe(),本節(jié)討論Linuxxbeads中擴(kuò)展的異步信號(hào)函數(shù),包括pthread_sigmask()、pthreadkill()和sigwait()三個(gè)函數(shù)。

所有的POSIX異步信號(hào)函數(shù)對(duì)于線程都是可用的。intpthread_slgmask(inthow,constsigset_t*newmask,sigset_t*oldm

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論