Linux 第10章 線程控制_第1頁
Linux 第10章 線程控制_第2頁
Linux 第10章 線程控制_第3頁
Linux 第10章 線程控制_第4頁
Linux 第10章 線程控制_第5頁
已閱讀5頁,還剩79頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡介

第10章線程控制

計算機(jī)在運(yùn)行時其執(zhí)行過程從大到小可以分為作業(yè)、進(jìn)程和線程3個級別。線程是系統(tǒng)分配處理器時間資源的基本單元,或者說是進(jìn)程之內(nèi)獨(dú)立執(zhí)行的一個單元。對于操作系統(tǒng)而言,其調(diào)度單元是線程。Linux中的線程是輕量級線程(lightweightthread),Linux中的線程調(diào)度是由內(nèi)核調(diào)度程序完成的,每個線程都有自己的ID號。與進(jìn)程相比,線程所消耗的系統(tǒng)資源較少、創(chuàng)建較快、相互間的通信也比較容易。

Linux線程10.1創(chuàng)建線程

10.2線程屬性

10.3線程等待終止10.4私有數(shù)據(jù)10.5線程同步10.6出錯處理10.7思考與練習(xí)

10.810.1Linux線程線程是在共享內(nèi)存空間中并發(fā)的多道執(zhí)行路徑,它們共享一個進(jìn)程的資源。在兩個普通進(jìn)程(非線程)間進(jìn)行切換時,內(nèi)核準(zhǔn)備從一個進(jìn)程的上下文切換到另一個進(jìn)程的上下文要有很大的花費(fèi),包括保存老進(jìn)程CPU狀態(tài),并加載新進(jìn)程的保存狀態(tài),用新進(jìn)程的內(nèi)存映像替換老進(jìn)程的內(nèi)存映像。線程也有其私有數(shù)據(jù)信息,包括:線程號(threadID):每個線程都有一個唯一的線程號與之對應(yīng)。寄存器(包括程序計數(shù)器和堆棧指針)。堆棧信號掩碼優(yōu)先級線程私有的存儲空間。多線程具有以下的優(yōu)點(diǎn):(1)提高應(yīng)用程序的響應(yīng)速度。將耗時長的操作置于一個新的線程,可以避免系統(tǒng)的等待。(2)使多CPU系統(tǒng)更有效。操作系統(tǒng)會保證當(dāng)線程數(shù)目不大于CPU數(shù)目時不同的線程運(yùn)行于不同的CPU上。(3)改善程序結(jié)構(gòu)。一個長而復(fù)雜的進(jìn)程可以分為多個線程,成為獨(dú)立運(yùn)行的部分。10.1.1線程和進(jìn)程的關(guān)系Linux是支持多線程的,在一個進(jìn)程內(nèi)生成多個線程。一個進(jìn)程可以擁有一個或多個線程。線程和進(jìn)程二者之間的關(guān)系有以下幾點(diǎn)。(1)首先,線程采用了多個線程可共享資源的設(shè)計思想。在多進(jìn)程情況下,每個進(jìn)程都有自己獨(dú)立的地址空間,在多線程情況下,同一進(jìn)程內(nèi)的線程共享進(jìn)程的地址空間。線程和進(jìn)程的最大區(qū)別在于線程完全共享相同的地址空間,運(yùn)行在同一地址上。(2)其次,由于進(jìn)程地址空間獨(dú)立而線程共享地址空間,所以從一個線程切換到另一線程所花費(fèi)的代價比進(jìn)程低。(3)再次,進(jìn)程本身的信息在內(nèi)存中占用的空間比線程大。因此,線程更能充分地利用內(nèi)存。線程可以看作是在進(jìn)程內(nèi)部執(zhí)行的指定序列。(4)最后,線程間的通信比進(jìn)程間的通信更加方便和省時。進(jìn)程間的數(shù)據(jù)空間相互獨(dú)立,彼此通信要以專門的通信方式進(jìn)行,通信時必須經(jīng)過操作系統(tǒng),而同一進(jìn)程的多個線程共享數(shù)據(jù)空間,一個線程的數(shù)據(jù)可以直接提供給其他線程使用,不必進(jìn)過操作系統(tǒng)。10.1.2線程分類內(nèi)核線程Linux內(nèi)核可以看作一個服務(wù)進(jìn)程(管理軟硬件資源,響應(yīng)用戶進(jìn)程的種種合理以及不合理的請求)。內(nèi)核需要多個執(zhí)行流并行,為了防止可能的阻塞,多線程化是必要的。內(nèi)核線程就是內(nèi)核的分身,一個分身可以處理一件特定事情。Linux內(nèi)核使用內(nèi)核線程來將內(nèi)核分成幾個功能模塊,像kswapd、kflushd等,這在處理異步事件如異步IO時特別有用。內(nèi)核線程的使用是廉價的,唯一使用的資源就是內(nèi)核棧和上下文切換時保存寄存器的空間。支持多線程的內(nèi)核叫做多線程內(nèi)核(Multi-Threadskernel)。內(nèi)核線程的調(diào)度由內(nèi)核負(fù)責(zé),一個內(nèi)核線程處于阻塞狀態(tài)時不影響其他的內(nèi)核線程,因為其是調(diào)度的基本單位。這與用戶線程是不一樣的。用戶線程用戶線程在用戶空間中實現(xiàn),允許多線程的程序運(yùn)行時不需要特定的內(nèi)核支持,內(nèi)核不需要直接對用戶線程進(jìn)程調(diào)度,內(nèi)核的調(diào)度對象和傳統(tǒng)進(jìn)程一樣,還是進(jìn)程本身,內(nèi)核并不知道用戶線程的存在。用戶線程的優(yōu)點(diǎn)如下:(1)某些線程操作的系統(tǒng)消耗大大減少。比如,對屬于同一個進(jìn)程的線程之間進(jìn)行調(diào)度切換時,不需要調(diào)用系統(tǒng)調(diào)用,因此將減少額外的消耗,一個進(jìn)程往往可以啟動上千個線程。(2)用戶線程的實現(xiàn)方式可以被定制或修改,以適應(yīng)特殊應(yīng)用的要求。它對于多媒體實時過程等尤其有用。另外,用戶線程可以比內(nèi)核線程實現(xiàn)方法默認(rèn)情況支持更多的線程。

系統(tǒng)創(chuàng)建線程的順序如下:當(dāng)一個進(jìn)程啟動后,它會自動創(chuàng)建一個線程即主線程(mainthread)或者初始化線程(initialthread),然后利用pthread_initialize()初始化系統(tǒng)管理線程并且啟動線程機(jī)制。線程機(jī)制啟動以后需要創(chuàng)建線程,需要phread_create()向管理線程發(fā)送REQ_CREATE請求,調(diào)用pthread_handle_create()創(chuàng)建新線程。10.2創(chuàng)建線程

線程的創(chuàng)建通過函數(shù)pthread_create()來完成,該函數(shù)的原型如下:#include<pthread.h>int

pthread_create(pthread_t*thread,pthread_attr_t

*attr,void*(*start_routine)(void*),void*arg);該函數(shù)創(chuàng)建線程,并為其分配一個唯一的標(biāo)識符pthread_t。調(diào)用者提供一個將由該線程執(zhí)行的函數(shù),該調(diào)用還可以為線程顯式指定一些屬性。表10-1創(chuàng)建線程其他系統(tǒng)函數(shù)函數(shù)說明pthread_t

pthread_self(void)獲取本線程的線程IDint

pthread_equal(pthread_tthread1,pthread_tthread2)判斷兩個線程ID是否指向同一線程int

pthread_once(pthread_once_t*once_control,void(*init_routine)(void))用來保證init_routine線程函數(shù)在進(jìn)程中僅執(zhí)行一次?!纠?0-1】創(chuàng)建線程。設(shè)計步驟[1]在Vim中創(chuàng)建一個新工程文件,命名為“example10_1.c”。[2]在“example10_1.c”中創(chuàng)建代碼如下所示。#include<stdlib.h>#include<stdio.h>#include<pthread.h>voidthread(){inti;for(i=0;i<3;i++)printf("Thisisapthread.\n");}intmain(){pthread_tid;int

i,ret;ret=pthread_create(&id,NULL,(void*)thread,NULL);if(ret!=0){printf("Createpthreaderror!\n");exit(1);}for(i=0;i<3;i++)printf("Thisisthemainprocess.\n");pthread_join(id,NULL);return(0);}用GCC編譯運(yùn)行程序結(jié)果如圖10-1所示。圖10-1創(chuàng)建線程10.3線程屬性

創(chuàng)建線程函數(shù)pthread_create()的第二個參數(shù)用來指定線程的屬性。線程屬性主要有綁定屬性、分離屬性、堆棧地址、堆棧大小、優(yōu)先級等。其中系統(tǒng)默認(rèn)的是非邦定、非分離、缺省1M的堆棧、與父進(jìn)程同樣級別的優(yōu)先級。(1)綁定屬性。在Linux中,采用的是“一對一”的線程機(jī)制。也就是一個用戶線程對應(yīng)一個內(nèi)核線程。綁定屬性就是指一個用戶線程固定地分配給一個內(nèi)核線程,因為CPU時間片的調(diào)度是面向內(nèi)核線程(輕量級進(jìn)程)的,因此具有綁定屬性的線程可以保證在需要的時候總有一個內(nèi)核線程與之對應(yīng),而與之對應(yīng)的非綁定屬性就是指用戶線程和內(nèi)核線程的關(guān)系不是始終固定的,而是由系統(tǒng)來分配。(2)分離屬性。分離屬性是決定線程以一個什么樣的方式來終止自己。在非分離情況下,當(dāng)一個線程結(jié)束時,它多占用的線程沒有得到釋放,也就是沒有真正的終止,需要通過pthread_join來釋放資源。而在分離屬性情況下,一個線程結(jié)束時會立即釋放它所占有的系統(tǒng)資源。但這里有一點(diǎn)要注意的是,如果設(shè)置一個線程分離屬性,而這個線程又運(yùn)行得非??斓脑挘敲此芸赡茉趐thread_create函數(shù)返回之前就終止了線程函數(shù)的運(yùn)行,它終止以后就很有可能將線程號和系統(tǒng)資源移交給其他的線程使用,這時調(diào)用pthread_create()的線程就得到錯誤的線程號。1.設(shè)置/獲取detachstate

屬性detachstate

屬性表示新創(chuàng)建的線程與進(jìn)程中其它線程是處于分離狀態(tài)還是可連接狀態(tài)。其合法值包括:PTHREAD_CREATE_DETACHED:此選項使得使用attr

創(chuàng)建的所有線程處于分離狀態(tài)。線程終止時,系統(tǒng)將自動回收與帶有此狀態(tài)的線程相關(guān)聯(lián)的資源。調(diào)用使用此屬性創(chuàng)建的pthread_detach()或pthread_join()函數(shù)將導(dǎo)致錯誤。PTHREAD_CREATE_JOINABLE:此選項使得使用attr

創(chuàng)建的所有線程處于可連接狀態(tài)。線程終止時,不會回收與帶有此狀態(tài)的線程相關(guān)聯(lián)的資源。要回收系統(tǒng)資源應(yīng)用程序必須調(diào)用使用此屬性創(chuàng)建的線程的pthread_detach()或pthread_join()函數(shù)。detachstate

的缺省值是PTHREAD_CREATE_JOINABLE。設(shè)置線程的detachstate

屬性的函數(shù)聲明如下:int

pthread_attr_setdetachstate(pthread_attr_t*attr,int

detachstate);/*用于設(shè)置已初始化屬性對象attr

中的detachstate

屬性*/獲取線程detachstate屬性的函數(shù)聲明如下:int

pthread_attr_getdetachstate(pthread_attr_t*attr,int*detachstate);/*獲取detachstate

屬性*/2.設(shè)置/獲取guardsize

屬性guardsize

屬性允許應(yīng)用程序指定使用此屬性對象創(chuàng)建的線程的守護(hù)區(qū)大小。所指定的守護(hù)區(qū)大小的單位為字節(jié)。大多數(shù)系統(tǒng)將守護(hù)區(qū)大小向上舍入為系統(tǒng)可配置變量PAGESIZE的倍數(shù)。如果指定了零值,則不會創(chuàng)建守護(hù)區(qū)。為應(yīng)用程序提供了guardsize

屬性的作用為:溢出保護(hù)可能會導(dǎo)致系統(tǒng)資源浪費(fèi)。如果應(yīng)用程序創(chuàng)建大量線程,并且已知這些線程永遠(yuǎn)不會溢出其棧,則可以關(guān)閉溢出保護(hù)區(qū)。通過關(guān)閉溢出保護(hù)區(qū),可以節(jié)省系統(tǒng)資源。l線程在棧上分配大型數(shù)據(jù)結(jié)構(gòu)時,可能需要較大的溢出保護(hù)區(qū)來檢測棧溢出。設(shè)置線程的guardsize屬性的函數(shù)聲明如下:int

pthread_attr_setguardsize(pthread_attr_t*attr,size_t

guardsize);/*用于設(shè)置已初始化屬性對象attr中的guardsize屬性值*/獲取線程guardsize屬性的函數(shù)聲明如下:int

pthread_attr_getguardsize(pthread_attr_t*attr,size_t*guardsize);/*獲取guardsize屬性*/3.設(shè)置/獲取schedparam

屬性schedparam

屬性的合法值因調(diào)度策略的不同而異。對于SCHED_FIFO和SCHED_RR調(diào)度策略,只需要schedparam屬性的sched_priority

成員??梢酝ㄟ^sched_get_priority_max()和sched_get_priority_min()獲取sched_priority

的合法值范圍。其他調(diào)度策略的schedparam的必要內(nèi)容是不確定的。設(shè)置線程的schedparam屬性的函數(shù)聲明如下:int

pthread_attr_setschedparam(pthread_attr_t*attr,struct

sched_param

schedparam);/*用于設(shè)置已初始化屬性對象attr

中的schedparam

屬性*/獲取線程schedparam屬性的函數(shù)聲明如下:int

pthread_attr_getschedparam(pthread_attr_t*attr,struct

sched_param*schedparam);/*獲取schedparam屬性*/4.設(shè)置/獲取schedpolicy

屬性schedpolicy

屬性允許通過此屬性對象創(chuàng)建的線程使用特定的調(diào)度策略,包括SCHED_OTHER(正常、非實時)、SCHED_RR(實時、輪轉(zhuǎn)法)和SCHED_FIFO(實時、先入先出)等3種,缺省為SCHED_OTHER。設(shè)置線程的schedpolicy屬性的函數(shù)聲明如下:int

pthread_attr_setschedpolicy(pthread_attr_t*attr,intpolicy);/*用于設(shè)置已初始化屬性對象attr

中的schedpolicy

屬性*/獲取線程schedpolicy屬性的函數(shù)聲明如下:int

pthread_attr_getschedpolicy(pthread_attr_t*attr,int*policy);/*獲取schedpolicy

屬性*/5.設(shè)置/獲取inheritsched

屬性inheritsched

屬性選用是從創(chuàng)建線程中繼承還是從此屬性對象中獲得調(diào)度策略及關(guān)聯(lián)屬性。inheritsched

有兩種值可供選擇:PTHREAD_INHERIT_SCHED:此選項指定從創(chuàng)建線程中繼承調(diào)度策略及關(guān)聯(lián)屬性。如果使用attr

創(chuàng)建了線程,將忽略attr

參數(shù)中的調(diào)度策略和關(guān)聯(lián)屬性。PTHREAD_EXPLICIT_SCHED:此選項指定,從此屬性對象中獲得已創(chuàng)建線程的調(diào)度策略及關(guān)聯(lián)屬性。設(shè)置線程的inheritsched屬性的函數(shù)聲明如下:int

pthread_attr_setinheritsched(pthread_attr_t*attr,intinherit);/*用于設(shè)置已初始化屬性對象attr

中的inheritsched

屬性*/獲取線程inheritsched屬性的函數(shù)聲明如下:int

pthread_attr_getinheritsched(pthread_attr_t*attr,int*inherit)/*獲取inheritsched屬性*/6.設(shè)置/獲取scope屬性scope屬性用來設(shè)置創(chuàng)建線程的競爭范圍,其合法值包括:PTHREAD_SCOPE_SYSTEM:使用此競爭范圍創(chuàng)建的線程,將與系統(tǒng)中(以及同一調(diào)度域中)的其他線程競爭資源。此屬性一般用于表示用戶線程應(yīng)該直接綁定到內(nèi)核調(diào)度實體。PTHREAD_SCOPE_PROCESS:使用此競爭范圍創(chuàng)建的線程,將直接與其進(jìn)程內(nèi)使用此調(diào)度競爭范圍創(chuàng)建的其他線程爭用資源。此屬性一般用于表示不應(yīng)綁定用戶線程(不綁定到任何特定的內(nèi)核調(diào)度的實體)。設(shè)置線程的scope屬性的函數(shù)聲明如下:int

pthread_attr_setscope(pthread_attr_t*attr,intscope);/*用于設(shè)置已初始化屬性對象attr

中的contentionscope

屬性*/獲取線程scope屬性的函數(shù)聲明如下:int

pthread_attr_getscope(pthread_attr_t*attr,int*scope); /*獲取scope屬性*/7.設(shè)置/獲取stackaddr屬性此屬性選項指定創(chuàng)建的線程將要使用的堆棧地址。應(yīng)用程序全面負(fù)責(zé)這些堆棧的分配、管理和取消分配。存儲分配的選項為malloc(3C)、brk(2)和mmap(2)函數(shù)。如果使用此選項,則只能使用此屬性對象創(chuàng)建一個線程。如果創(chuàng)建了多個線程,它們將使用同一個堆棧。stackaddr

屬性的缺省值為NULL。如果線程用戶堆棧的存儲不是由庫分配的(也就是說,stackaddr

屬性不是NULL),將忽略guardsize

屬性。設(shè)置線程的stackaddr屬性的函數(shù)聲明如下:int

pthread_attr_setstackaddr(pthread_attr_t*attr,void*stackaddr);/*用于設(shè)置已初始化屬性對象attr

中的stackaddr

屬性*/獲取線程stackaddr屬性的函數(shù)聲明如下:int

pthread_attr_getstackaddr(pthread_attr_t*attr,void**stackaddr);/*獲取stackaddr屬性*/8.設(shè)置/獲取stacksize屬性stacksize

屬性用來設(shè)置創(chuàng)建線程的用戶堆棧大小。其合法值包括:PTHREAD_STACK_MIN:此選項指定,使用此屬性對象創(chuàng)建的線程的用戶堆棧大小將使用缺省堆棧大小。此值為某個線程所需的最小堆棧大?。ㄒ宰止?jié)為單位)。對于所有線程來說,這個最小值可能無法接受。

stacksize:具體的大小。定義使用此屬性對象創(chuàng)建的線程的用戶堆棧大小(以字節(jié)為單位)。此值必須大于或等于最小堆棧大小PTHREAD_STACK_MIN。設(shè)置線程的stacksize屬性的函數(shù)聲明如下:int

pthread_attr_setstacksize(pthread_attr_t*attr,size_t

stacksize);/*用于設(shè)置已初始化屬性對象attr

中的stacksize

屬性*/獲取線程stacksize屬性的函數(shù)聲明如下:int

pthread_attr_getstacksize(pthread_attr_t*_attr,size_t*stacksize);/*獲取stacksize屬性*/9.設(shè)置/獲取guardsize屬性guardsize

屬性用來警戒堆棧的大小。設(shè)置線程的guardsize屬性的函數(shù)聲明如下:int

pthread_attr_setguardsize(pthread_attr_t*attr,size_t

guardsize);/*用于設(shè)置已初始化屬性對象attr

中的guardsize

屬性*/獲取線程guardsize屬性的函數(shù)聲明如下:int

pthread_attr_getguardsize(pthread_attr_t*_attr,size_t*guardsize);/*獲取guardsize屬性*/【例10-2】線程屬性。設(shè)計步驟[1]在Vim中創(chuàng)建一個新工程文件,命名為“example10_2.c”。[2]在“example10_2.c”中創(chuàng)建代碼如下所示。#include<stdio.h>#include<pthread.h>#include<sched.h>voidmyfunction(){inti;for(i=0;i<3;i++)printf("Thisisapthread.\n");}voidmain(){pthread_attr_t

attr;pthread_t

tid;struct

sched_param

param;int

newprio=20;pthread_attr_init(&attr);pthread_attr_getschedparam(&attr,¶m);param.sched_priority=newprio;pthread_attr_setschedparam(&attr,¶m);pthread_create(&tid,&attr,(void*)myfunction,NULL);printf("Success.\n");}

[3]用GCC編譯并運(yùn)行程序,其結(jié)果如圖10-2所示。圖10-2設(shè)置線程屬性10.4線程等待終止1、線程等待線程的等待通過函數(shù)pthread_join()來實現(xiàn),該函數(shù)定義為:#include<pthread.h>int

pthread_join(pthread_t_th,void**_thread_return)該函數(shù)用來等待一個線程的結(jié)束,第1個參數(shù)為被等待的線程標(biāo)識符,第2個參數(shù)為一個用戶定義的指針,它可以用來存儲被等待線程的返回值。這個函數(shù)是一個線程阻塞函數(shù),調(diào)用它的函數(shù)將一直等待到被等待的線程結(jié)束為止,當(dāng)函數(shù)返回時,處于等待狀態(tài)的線程資源被收回。2、線程終止新創(chuàng)建的線程從執(zhí)行用戶定義的函數(shù)處開始執(zhí)行,直到出現(xiàn)以下情況時退出:(1)執(zhí)行完成后隱式退出;(2)由線程本身顯示調(diào)用pthread_exit()函數(shù)退出;它的定義如下:#include<pthread.h>voidpthread_exit(void*retval);函數(shù)pthread_exit()終止調(diào)用的線程。參數(shù)retval的值對pthread_join()函數(shù)的成功有實際意義。然而,pthread_exit()的retval必須指定,在線程退出時它才退出的數(shù)據(jù),因此它不能夠作為正在退出的線程的自動局部數(shù)據(jù)被分配。函數(shù)pthread_exit在成功調(diào)用時返回0,失敗時返回-1。10.5私有數(shù)據(jù)

在多線程環(huán)境下,進(jìn)程內(nèi)的所有線程共享進(jìn)程的數(shù)據(jù)空間,因此全局變量為所有線程共享,在程序設(shè)計中有時候需要保存線程自己的全局變量,這種特殊的變量僅在某個線程內(nèi)部有效。線程私有數(shù)據(jù)采用了一種被稱為一鍵多值的技術(shù),即一個鍵對應(yīng)多個值。訪問數(shù)據(jù)時都是通過鍵值來訪問,好像是對一個鍵值訪問。操作線程私有數(shù)據(jù)的函數(shù)主要有四個:pthread_key_create()(創(chuàng)建一個鍵),pthread_setspecific()(為一個鍵設(shè)置線程私有數(shù)據(jù)),pthread_getspecific()(從一個鍵讀取線程私有數(shù)據(jù)),phread_key_delete()(刪除一個鍵)。這些函數(shù)的聲明如下:#include<pthread.h>int

pthread_key_create(pthread_key_t*key,void(*destr_function)(void*));int

pthread_setspecific(pthread_key_t

key,constvoid*pointer);void*pthread_getspecific(pthread_key_tkey);int

phread_key_delete(pthread_key_tkey);

pthread_key_create():從Linux的TSD池中分配一項,將其值賦給key供以后訪問使用,它的第1個參數(shù)key為指向鍵值的指針,第2個參數(shù)為一個函數(shù)指針,如果指針不為空,則在線程退出時將以key所關(guān)聯(lián)的數(shù)據(jù)為參數(shù)調(diào)用destr_function(),釋放分配的緩沖區(qū)。

pthread_setspecific():該函數(shù)將pointer的值與key相關(guān)聯(lián)。用pthread_setspecific為一個鍵指定新的線程數(shù)據(jù)時,線程必須先釋放原有的線程數(shù)據(jù)以回收空間。

pthread_getspecific():通過該函數(shù)得到與key相關(guān)聯(lián)的數(shù)據(jù)。

phread_key_delete():該函數(shù)用來刪除一個鍵,刪除后,鍵所占用的內(nèi)存將被釋放。需要注意的是,鍵占用的內(nèi)存被釋放,與該鍵關(guān)聯(lián)的線程數(shù)據(jù)所占用的內(nèi)存并不被釋放。因此,線程數(shù)據(jù)的釋放必須在釋放鍵之前完成。10.6線程同步如果所涉及的線程是獨(dú)立的,而且異步執(zhí)行,也就是說每個線程都包含了運(yùn)行時自身所需要的數(shù)據(jù)或方法,而不需要外部的資源或方法,也不必關(guān)心其他線程的狀態(tài)或行為。但是,有時候在進(jìn)行多線程的程序設(shè)計中需要實現(xiàn)多個線程共享同一段代碼。這時,由于線程和線程之間互相競爭CPU資源,為了解決這個問題,必須要引入同步機(jī)制。在Linux系統(tǒng)中提供了多種處理線程同步問題的方式,其中最常用的有互斥鎖、條件變量和信號量?;コ怄i通過鎖機(jī)制來實現(xiàn)線程間的同步,互斥鎖從本質(zhì)上說就是一把鎖,提供對共享資源的保護(hù)訪問?;コ怄i具有以下三個特性:原子性:如果一個線程鎖定了一個互斥量,那么臨界區(qū)內(nèi)的操作要么全部完成,要么一個也不執(zhí)行。唯一性:如果一個線程鎖定了一個互斥量,那么在它解除鎖定之前,沒有其他線程可以鎖定這個互斥量。非繁忙等待:如果一個線程已經(jīng)鎖定了一個互斥量,第二個線程又試圖去鎖定這個互斥量,則第二個線程將被掛起(不占用任何CPU資源),直到第一個線程解除對這個互斥量的鎖定為止。第二個線程被喚醒并繼續(xù)執(zhí)行,同時鎖定這個互斥量。10.6.1互斥鎖表10-2互斥鎖函數(shù)函數(shù)功能pthread_mutex_init初始化一個互斥鎖pthread_mutex_destroy注銷一個互斥鎖pthread_mutex_lock加鎖,如果不成功,阻塞等待pthread_mutex_unlock解鎖pthread_mutex_trylock測試加鎖,如果不成功則立即返回1.初始化在Linux下,線程的互斥鎖在使用前,要對它進(jìn)行初始化。對于靜態(tài)分配的互斥鎖,可以把它設(shè)置為PTHREAD_MUTEX_INITIALIZER,或者調(diào)用pthread_mutex_init()。操作語句如下:pthread_mutex_t

mutex=PTHREAD_MUTEX_INITIALIZER;對于動態(tài)分配的互斥量,在申請內(nèi)存(malloc)之后,通過pthread_mutex_init()進(jìn)行初始化,并且在釋放內(nèi)存(free)前需要調(diào)用pthread_mutex_destroy()來注銷互斥鎖。操作語句如下:int

pthread_mutex_init(pthread_mutex_t*restrictmutex,constpthread_mutexattr_t*restric

attr);int

pthread_mutex_destroy(pthread_mutex_t*mutex);返回值:成功則返回0,出錯則返回錯誤編號。表10-3互斥鎖的屬性

屬性值意義PTHREAD_MUTEX_TIMED_NP普通鎖,當(dāng)一個線程加鎖后,其余請求鎖的線程形成等待隊列,解鎖后按優(yōu)先級獲得鎖PTHREAD_MUTEX_RECURSIVE_NP嵌套鎖,允許一個線程對同一個鎖多次加鎖,并通過多次unlock解鎖,如果不是同線程請求,則在解鎖時重新競爭PTHREAD_MUTEX_ERRORCHECK_NP檢錯鎖,解鎖后重新競爭PTHREAD_MUTEX_ADAPTIVE_NP適應(yīng)鎖2.互斥操作初始化以后就可以對互斥鎖進(jìn)行加鎖,如果互斥鎖已經(jīng)上了鎖,調(diào)用線程會阻塞,直到被解鎖。首先說一下加鎖函數(shù)。原型:int

pthread_mutex_lock(pthread_mutex_t*mutex);int

pthread_mutex_trylock

(pthread_mutex_t*mutex);返回值:成功則返回0,出錯則返回錯誤編號。其中pthread_mutex_trylock()函數(shù)是非阻塞調(diào)用模式,如果互斥鎖沒被鎖住,pthread_mutex_trylock()函數(shù)將進(jìn)行加鎖,并獲得對共享資源的訪問權(quán)限;如果互斥鎖被鎖住了,pthread_mutex_trylock()函數(shù)將不會阻塞等待而直接返回忙狀態(tài)。解鎖函數(shù)pthread_mutex_unlock():原型:

int

pthread_mutex_unlock

(pthread_mutex_t*mutex);返回值:成功則返回0,出錯則返回錯誤編號。3.死鎖死鎖主要發(fā)生在有多個依賴鎖存在時,會在一個線程試圖以與另一個線程相反順序鎖住互斥鎖時發(fā)生。死鎖是使用互斥鎖時應(yīng)該盡量避免的,pthread庫可以跟蹤這種情形,最后一個線程試圖調(diào)用pthread_mutex_lock()時會失敗,并返回類型為EDEADLK的錯誤?!纠?0-3】互斥鎖。設(shè)計步驟[1]在Vim中創(chuàng)建一個新工程文件,命名為“example10_3.c”。[2]在“example10_3.c”中創(chuàng)建代碼如下所示。

#include<stdio.h>#include<pthread.h>#include<sched.h>voidreader_function(void);voidwriter_function(void);charbuffer;int

buffer_has_item=0;pthread_mutex_t

mutex;main(void){pthread_treader;//delay.tv_nec=0;/*用默認(rèn)屬性初始化一個互斥鎖對象*/pthread_mutex_init(&mutex,NULL);pthread_create(&reader,NULL,(void*)reader_function,NULL);writer_function();}voidwriter_function(void){printf("writer_functionbegin.\n",buffer);inti=0;while(1){/*鎖定互斥鎖*/pthread_mutex_lock(&mutex);if(buffer_has_item==0){buffer='a'+i;i=i+1;buffer_has_item=1;}/*打開互斥鎖*/pthread_mutex_unlock(&mutex);sleep(1);}}voidreader_function(void){printf("reader_functionbegin.\n",buffer);while(1){pthread_mutex_lock(&mutex);if(buffer_has_item==1){printf("buffer=%c\n",buffer);buffer_has_item=0;}pthread_mutex_unlock(&mutex);sleep(1);}}使用GCC編譯并運(yùn)行程序,結(jié)果如圖10-3所示。圖10-3互斥鎖操作結(jié)果

互斥鎖一個明顯的缺點(diǎn)是它只有兩種狀態(tài):鎖定和非鎖定。而條件變量通過允許線程阻塞和等待另一個線程發(fā)送信號的方法彌補(bǔ)了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變量被用來阻塞一個線程,當(dāng)條件不滿足時,線程往往解開相應(yīng)的互斥鎖并等待條件發(fā)生變化。一旦其它的某個線程改變了條件變量,它將通知相應(yīng)的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖并重新測試條件是否滿足。10.6.2條件變量表10-4對條件變量進(jìn)行操作的函數(shù)函數(shù)功能Pthread_cond_init()初始化條件變量Pthread_cond_wait()基于條件變量阻塞,無條件等待Pthread_cond_timedwait()阻塞直到指定事件發(fā)生,計時等待Pthread_cond_signal()解除特定線程的阻塞,存在多個扥帶線程時按入隊順序激活其中一個Pthread_cond_broadcast()解除所有線程的阻塞Pthread_cond_destroy()清除條件變量條件變量采用的數(shù)據(jù)類型是pthread_cond_t,在使用之前必須要進(jìn)行初始化。條件變量和互斥鎖一樣,也有靜態(tài)動態(tài)兩種創(chuàng)建方式,靜態(tài)方式使用PTHREAD_COND_INITIALIZER常量,如下:pthread_cond_t

cond=PTHREAD_COND_INITIALIZER;動態(tài)方式調(diào)用pthread_cond_init()函數(shù),其原型為:int

pthread_cond_init(pthread_cond_t*cond,pthread_condattr_t*cond_attr)其中cond是一個指向結(jié)構(gòu)pthread_cond_t的指針,cond_attr是一個指向結(jié)構(gòu)pthread_condattr_t的指針。結(jié)構(gòu)pthread_condattr_t是條件變量的屬性結(jié)構(gòu),和互斥鎖一樣可以用它來設(shè)置條件變量是進(jìn)程內(nèi)可用還是進(jìn)程間可用,默認(rèn)值是PTHREAD_PROCESS_PRIVATE,即此條件變量被同一進(jìn)程內(nèi)的各個線程使用。注意初始化條件變量只有未被使用時才能重新初始化或被釋放。函數(shù)pthread_cond_wait()使線程阻塞在一個條件變量上。它的函數(shù)原型為:int

pthread_cond_wait(pthread_cond_t*cond,pthread_mutex_t*__mutex);線程解開mutex指向的鎖并被條件變量cond阻塞,直到條件被信號喚醒。通常條件表達(dá)式在互斥鎖的保護(hù)下求值,如果條件表達(dá)式為假,線程基于條件表達(dá)式阻塞。當(dāng)一個線程改變條件變量值時,條件變量獲得一個信號,使得等待條件變量的線程退出等待狀態(tài)。另一個用來阻塞線程的函數(shù)是pthread_cond_timedwait(),它的原型為:int

pthread_cond_timedwait(pthread_cond_t*cond,pthread_mutex_t*mutex,conststruct

timespec*abstime);它比函數(shù)pthread_cond_wait()多了一個時間參數(shù),經(jīng)歷abstime段時間后,即使條件變量不滿足,阻塞也被解除。線程可以被函數(shù)pthread_cond_signal()和函數(shù)pthread_cond_broadcast()喚醒,但是要注意的是,條件變量只是起阻塞和喚醒線程的作用,具體的判斷條件還需用戶給出。線程被喚醒后,它將重新檢查判斷條件是否滿足,如果還不滿足,一般說來線程應(yīng)該仍阻塞在這里,被等待被下一次喚醒。這個過程一般用while語句實現(xiàn)。函數(shù)pthread_cond_signal()的原型為:int

pthread_cond_signal(pthread_cond_t*cond);它用來釋放被阻塞在條件變量cond上的一個線程。多個線程阻塞在此條件變量上時,哪一個線程被喚醒是由線程的調(diào)度策略所決定的。函數(shù)pthread_cond_broadcast()激活所有等待線程,其函數(shù)原型為:int

pthread_cond_broadcast(pthread_cond_t*cond);當(dāng)一個條件變量不再使用時,需要將其清除,清除一個條件變量使用函數(shù)pthread_cond_destroy()來實現(xiàn),其函數(shù)原型為:int

pthread_cond_destroy(pthread_cond_t*cond);【例10-4】條件變量。設(shè)計步驟[1]在Vim中創(chuàng)建一個新工程文件,命名為“example10_4.c”。[2]在“example10_4.c”中創(chuàng)建代碼如下所示。#include<stdlib.h>#include<stdio.h>#include<pthread.h>#include<sched.h>pthread_mutex_t

count_lock;pthread_cond_t

count_nonzero;unsignedcount;decrement_count(){printf("Createdecrement_count

pthreadsuccess!\n");pthread_mutex_lock(&count_lock);while(count==0)pthread_cond_wait(&count_nonzero,&count_lock);count=count-1;printf("Countisdecrement:count=%d\n",count);pthread_mutex_unlock(&count_lock);}increment_count(){printf("Createincrement_count

pthreadsuccess!\n");pthread_mutex_lock(&count_lock);if(count==0)pthread_cond_signal(&count_nonzero);count=count+1;printf("Countisincrement:count=%d\n",count);pthread_mutex_unlock(&count_lock);}intmain(){pthread_tid;int

i,ret;ret=pthread_create(&id,NULL,(void*)decrement_count,NULL);if(ret!=0){printf("Createdecrement_count

pthreaderror!\n");exit(1);}ret=pthread_create(&id,NULL,(void*)increment_count,NULL);if(ret!=0){printf("Createincrement_count

pthreaderror!\n");exit(1);}}用GCC編譯運(yùn)行程序結(jié)果如圖10-4所示。圖10-4條件變量例10-4的運(yùn)行結(jié)果1965年,E.W.Dijkstra提出了信號量的概念,之后信號量成為操作系統(tǒng)實現(xiàn)互斥和同步的一種普遍機(jī)制。信號量是一種特殊的變量,只能取正整數(shù)值,對這些正整數(shù)只能采取兩種操作,P操作(代表等待、關(guān)操作)、V操作(代表信號、開操作)。定義如下:P(sem):如果sem的值大于0,則sem減1,如果sem的值為0,則掛起該線程。V(sem):如果有其他進(jìn)程因等待sem而被掛起,則讓它恢復(fù)執(zhí)行,如果沒有線程等待sem而被掛起,則sem加上1。10.6.3信號量1、創(chuàng)建信號量在使用信號量之前,首先需要創(chuàng)建一個信號量。創(chuàng)建一個信號量的函數(shù)為semget(),其函數(shù)聲明如下:#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>int

semget(key_t,int

nsems,intflag);該函數(shù)用來打開一個新的信號量集合,或者打開一個已經(jīng)存在的信號量集合。其中,參數(shù)key表示所創(chuàng)建或打開的信號量集的鍵;參數(shù)nsems表示創(chuàng)建的信號量集中信號量的個數(shù),此參數(shù)只在創(chuàng)建一個新的信號量集時才有效;參數(shù)flag表示調(diào)用函數(shù)的操作類型,也可以用來設(shè)置信號量集的訪問權(quán)限。調(diào)用函數(shù)semget()的作用由參數(shù)key和flag決定,函數(shù)調(diào)用成功時返回值為信號量的引用標(biāo)識符,調(diào)用失敗時,返回-1。2、對信號量的操作對信號量的操作使用如下函數(shù):#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>int

semop(int

semid,struct

sembuf

semoparray[],size_t

nops);參數(shù)semid是信號量集的引用id,semoparray[]是一個sembuf類型數(shù)組,sembuf結(jié)構(gòu)用于指定調(diào)用semop()函數(shù)所作的操作,數(shù)組semoparray[]中的元素的個數(shù)由nops決定。sembuf結(jié)構(gòu)如下:struct

sembuf{

usbort

sem_num;shortsem_op;shortsem_flag;}其中,sem_num指定要操作的信號量;sem_flag為操作標(biāo)記;sem_op用于表示所執(zhí)行的操作,相應(yīng)取值的含義如下:sem_op>0:表示線程對資源使用完畢,交回該資源。此時信號量集的semid_ds結(jié)構(gòu)的sem_base.semval將加上sem_op的值。如果此時設(shè)置了SEM_UNDO位,則信號量的調(diào)整值將減去sem_op絕對值。sem_op=0:表示進(jìn)程要等待,直到sem_base.semval的值變?yōu)?。sem_op<0:表示進(jìn)程希望使用資源。此時將比較sem_base.semval和sem_op的絕對值的大小。3、對信號量的控制對信號量的控制操作是通過semctl()來山實現(xiàn)的,函數(shù)原型如下:#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>int

semctl(int

semid,int

semnum,int

cmd,unionsenum

arg);其中semid為信號量集的引用標(biāo)識符,semnum用于指定信號量集中某個特定的信號量,參數(shù)cmd表示調(diào)用該函數(shù)希望執(zhí)行的操作,參數(shù)arg是semun聯(lián)合。10.7出錯處理在軟件開發(fā)時需要時刻檢查錯誤發(fā)生的各種可能,例如創(chuàng)建進(jìn)程失敗、打開文件失敗等。當(dāng)錯誤發(fā)生時,程序應(yīng)當(dāng)給出提示信息并進(jìn)行相應(yīng)的處理。在調(diào)用庫函數(shù)或系統(tǒng)調(diào)用函數(shù)后,絕

溫馨提示

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

評論

0/150

提交評論