linux 线程管理、同步机制等
學(xué)了那么多有關(guān)進(jìn)程的東西,一個(gè)作業(yè)從一個(gè)進(jìn)程開(kāi)始,如果你需要執(zhí)行其他的東西你可以添加一些進(jìn)程,進(jìn)程之間可以通信、同步、異步。似乎所有的事情都可以做了。
對(duì)的,進(jìn)程是當(dāng)初面向執(zhí)行任務(wù)而開(kāi)發(fā)出來(lái)的,每個(gè)進(jìn)程代表著一個(gè)動(dòng)作,你可以說(shuō)一個(gè)進(jìn)程組代表一個(gè)任務(wù),或者一個(gè)會(huì)話代表一個(gè)任務(wù),關(guān)鍵是你的任務(wù)就是在進(jìn)程的執(zhí)行與進(jìn)程之間的交互中被完成。
但是,我們知道,進(jìn)程在操作系統(tǒng)中被設(shè)計(jì)成獨(dú)立的個(gè)體,進(jìn)程與進(jìn)程之間有絕對(duì)的界限,他們的通信是需要通過(guò)內(nèi)核的。這個(gè)耗費(fèi)是蠻大的,并且,進(jìn)程對(duì)資源的占有也時(shí)常是一個(gè)讓進(jìn)程編程負(fù)擔(dān)的原因。
總的來(lái)說(shuō),如果你希望系統(tǒng)中并行的運(yùn)行的一些任務(wù),你就需要跑多個(gè)進(jìn)程,而進(jìn)程它跑動(dòng)的前提是對(duì)資源的占有,它是一個(gè)獨(dú)立體,從某種意義上講,它的執(zhí)行不依賴其他的東西(當(dāng)然內(nèi)核的支持肯定是要的)。這種設(shè)計(jì)風(fēng)格,保證了進(jìn)程的獨(dú)立性,但是也增加了進(jìn)程的重量。其次,不管進(jìn)程獨(dú)立風(fēng)格怎么樣,進(jìn)程之間的通信始終是需要的。這種通信需要內(nèi)核的支持,這也是不夠理想的。
基于上面的問(wèn)題,線程的概念被提出來(lái)了。
沒(méi)有線程概念之前,進(jìn)程被看做是一個(gè)執(zhí)行與資源的最小占有體。進(jìn)程被看做執(zhí)行的一個(gè)最小體,什么意思呢,一個(gè)進(jìn)程就是用來(lái)服務(wù)一個(gè)請(qǐng)求的。多個(gè)請(qǐng)求需要?jiǎng)?chuàng)建多個(gè)進(jìn)程來(lái)服務(wù)請(qǐng)求。關(guān)鍵是,如果這些請(qǐng)求是類似的、關(guān)聯(lián)非常高的,這個(gè)時(shí)候,每個(gè)進(jìn)程對(duì)資源的占有,以及進(jìn)程之間的通信就非常頻繁。進(jìn)程顯然不適合這種模式。
線程,抽象的講就是將進(jìn)程這個(gè)作為執(zhí)行的個(gè)體拆分成若干個(gè)小執(zhí)行體,這些小的執(zhí)行體共享一個(gè)進(jìn)程所占有的資源。
如果一個(gè)進(jìn)程,是一個(gè)手術(shù)室,那么該手術(shù)室需要對(duì)很多工具進(jìn)行獨(dú)占,并且保持一定的獨(dú)立性(不獨(dú)立將會(huì)很危險(xiǎn),想想,不同的手術(shù)室,經(jīng)常為某個(gè)手術(shù)工具而等待,將會(huì)很恐怖)。手術(shù)間的通信也會(huì)經(jīng)過(guò)外面,而不是直接的。
而一個(gè)手術(shù)室內(nèi)部呢?有若干個(gè)醫(yī)生、護(hù)士,他們各司其職,工具共享、交流通暢。這就是進(jìn)程和線程的區(qū)別。
不過(guò)從另外一個(gè)角度上講,線程和進(jìn)程只是從不同的角度(粗細(xì)度)看操作系統(tǒng)的執(zhí)行體。就線程本身而言,它的最大特點(diǎn)就是共享一個(gè)進(jìn)程的幾乎所有資源(內(nèi)存空間等),但是作為一個(gè)執(zhí)行體,它也擁有與進(jìn)程類似的相關(guān)控制管理操作(創(chuàng)建、退出、等待、通信)。所以,認(rèn)清進(jìn)程與線程的區(qū)別的基礎(chǔ)上,看到他們的相同點(diǎn),是學(xué)習(xí)兩者的好方法。
線程基本管理
創(chuàng)建線程
/usr/include/bits/pthread.h
Extern int pthread_create( pthread_t * __restrict??__new thread,
????????????????????????????????__constpthread_attr_t*??__restrict??__attr,
????????????????????????????????Void * (* __start _routine ) (void *),
????????????????????????????????Void *??__restrict __arg)
/usr/include/bits/pthreadtypes.h
Typedef unsigned long int pthread_t;
可以看到,創(chuàng)建一個(gè)新的線程,需要注入一段“執(zhí)行腳本”,就像創(chuàng)建一個(gè)新的進(jìn)程一樣,復(fù)制父進(jìn)程的“執(zhí)行腳本”,或者使用exec函數(shù)從文件系統(tǒng)中調(diào)入一個(gè)新的執(zhí)行文件進(jìn)來(lái)。進(jìn)程是獨(dú)立體,自然“執(zhí)行腳本”也是獨(dú)立體。而線程被定義為進(jìn)程執(zhí)行體的一部分,所以,線程的“執(zhí)行腳本”自然是進(jìn)程“執(zhí)行腳本”的一個(gè)部分。
可知,上述所說(shuō)的執(zhí)行腳本,在編寫(xiě)程序的時(shí)候,就是程序本身,程序內(nèi)部最能代表一個(gè)相對(duì)獨(dú)立的執(zhí)行體的東西,自然是函數(shù)。所以,新建一個(gè)線程的所需要的東西就是指定一個(gè)該進(jìn)程(code)內(nèi)部的函數(shù)作為該線程的執(zhí)行腳本。從這點(diǎn)我們也可以看出,如果沒(méi)有線程的概念,進(jìn)程就像是一個(gè)按照“執(zhí)行腳本”(code)順序執(zhí)行的執(zhí)行體,線程的作用是從這個(gè)執(zhí)行體中摳出一個(gè)相對(duì)獨(dú)立的部分來(lái)執(zhí)行。有一定的異步性。從上層來(lái)講,一個(gè)進(jìn)程仍然是按照用戶編程的程序進(jìn)行跑著。但是線程似乎讓這個(gè)“執(zhí)行體”并發(fā)的進(jìn)行起來(lái)。
繼續(xù)使用手術(shù)室的例子,手術(shù)的過(guò)程就是一段手術(shù)的可執(zhí)行程序中的code所指示的。每個(gè)線程,取該code中的某個(gè)部分做該做的事。主刀者做它的事,輸血者做輸血的事,觀察者看儀器。他們各司其職、但也并是不保持絕對(duì)的獨(dú)立。
可以說(shuō),添加了線程概念的進(jìn)程,在執(zhí)行的時(shí)候,有一定的異步特點(diǎn)。
?
線程退出
與進(jìn)程的exit和abort對(duì)應(yīng)。一個(gè)線程在該線程內(nèi)部突出的方式有:
/usr/include/bits/pthreadtypes.h
Extern void pthread_exit (void * _retval);
線程退出的情景有:
1)?調(diào)用pthread_exit
2)?調(diào)用pthread_cancel
3)?所屬進(jìn)程退出
4)?線程函數(shù)結(jié)束
5)?其中的一個(gè)線程執(zhí)行exec函數(shù)
要知道,exec函數(shù)的意思是喚起一個(gè)新的可執(zhí)行程序,來(lái)替換現(xiàn)有進(jìn)程,這是一個(gè)進(jìn)程級(jí)別的命令的,但是,在某個(gè)線程中執(zhí)行,所以一個(gè)線程執(zhí)行這個(gè)命令會(huì)導(dǎo)致其它的所有線程全部被退出。
等待線程
進(jìn)程中,父進(jìn)程可以使用wait和waitpid等待子進(jìn)程的執(zhí)行,線程中也是一樣:
Extern int pthread_join (pthread_t??__th, void??**??__thread_return);
獨(dú)立線程
使用等待線程,必須指定某個(gè)線程的id,必須是關(guān)聯(lián)的線程(自然是屬于一個(gè)進(jìn)程的),一個(gè)進(jìn)程內(nèi)部的線程默認(rèn)是與進(jìn)程關(guān)聯(lián)的,可以通過(guò)函數(shù)執(zhí)行,讓該進(jìn)程不關(guān)聯(lián)。
Extern int pthread_detach (pthread_t??__th);
?
線程獨(dú)占的資源
這里我們講的線程獨(dú)占資源,是指那些線程退出的時(shí)候會(huì)釋放的資源,其他的線程是無(wú)法訪問(wèn)的線程。其實(shí)這個(gè)問(wèn)題我們只要知道,線程是進(jìn)程中某個(gè)函數(shù)的異步執(zhí)行而已,就很容易推到出來(lái)。
一個(gè)進(jìn)程的內(nèi)存空間中,有code區(qū)、數(shù)據(jù)區(qū)、BCC區(qū),這三個(gè)區(qū)是可執(zhí)行程序在還未進(jìn)入內(nèi)存的時(shí)候就已經(jīng)分配好了的,所以,它是一個(gè)進(jìn)程的固有區(qū),進(jìn)入內(nèi)存后,再加上棧區(qū)和堆區(qū)。線程中有可能申請(qǐng)了全局或者靜態(tài)數(shù)據(jù),這些數(shù)據(jù)都會(huì)在數(shù)據(jù)區(qū)或者BCC區(qū)中,線程中同時(shí)會(huì)有可能申請(qǐng)堆上空間,這個(gè)在一個(gè)進(jìn)程內(nèi)部,是通用的,因?yàn)樵搮^(qū)的數(shù)據(jù)只受申請(qǐng)很釋放函數(shù)管理,與是哪個(gè)線程申請(qǐng),哪個(gè)釋放無(wú)關(guān)。在一個(gè)進(jìn)程內(nèi)部它還真的無(wú)所謂被誰(shuí)申請(qǐng)和釋放。
所以在一個(gè)進(jìn)程內(nèi)部,線程所獨(dú)占的,也就是局部變量,其實(shí)一個(gè)函數(shù)的角度去看,局部變量的確只屬于該函數(shù),程序的其它的部分根本不知道它的存在。
如果沒(méi)有線程概念的時(shí)候,這些局部變量被壓入在統(tǒng)一的棧中,但是,有了線程,這種方式有不通用了,我們知道“執(zhí)行腳本”執(zhí)行的最根本依靠就是“棧的”先入后出方式。不同的線程之間是異步的,所以他們不可能在共享一個(gè)棧(如果是同步的,仍然是可以的)。
解決的方法可以想到,在沒(méi)有線程的概念中,所以的過(guò)程都在棧中發(fā)生,一個(gè)函數(shù)的過(guò)程局部變量在棧的某個(gè)部分相對(duì)其他事物是獨(dú)立了,也就是說(shuō),其他部分僅會(huì)依賴你的開(kāi)始和結(jié)束,中間部分他們看不到。所以這個(gè)就可以獨(dú)立出來(lái)。
所以線程是可以獨(dú)立擁有自己的棧的。當(dāng)然線程除了擁有自己獨(dú)立的棧,還擁有其他獨(dú)立的東西。
線程退出前的動(dòng)作
與對(duì)于資源的占有,本該屬于進(jìn)程的動(dòng)作,進(jìn)程退出它所占有的所有資源都會(huì)被釋放,所以進(jìn)程在退出的時(shí)候無(wú)需太多考慮資源的釋放問(wèn)題(可能,有什么對(duì)某個(gè)資源的不同進(jìn)程之間的競(jìng)爭(zhēng),需要進(jìn)程內(nèi)部協(xié)調(diào))。
但是在一個(gè)進(jìn)程內(nèi)部,線程之間也會(huì)出現(xiàn)資源的競(jìng)爭(zhēng),這不要與線程共享進(jìn)程所占有的資源這個(gè)概念搞混淆。從另外一個(gè)角度上講,各個(gè)進(jìn)程可以看做對(duì)系統(tǒng)擁有的所有資源共享呢。共享是共享,但是真正要使用的時(shí)候,有些工具就只能一個(gè)執(zhí)行體使用。所以進(jìn)程內(nèi)部同樣存在資源競(jìng)爭(zhēng)問(wèn)題。只是范圍縮小了而已。
在進(jìn)程中,由于各個(gè)線程處于一個(gè)“空間內(nèi)”,有關(guān)資源競(jìng)爭(zhēng)的控制直接由線程來(lái)做,我們?cè)O(shè)想,有一個(gè)資源記錄薄,上面記錄的資源全部只能是線程獨(dú)的。那個(gè)線程想使用某個(gè)工具就需要到該記錄簿上,打個(gè)記號(hào),(如果已經(jīng)被人使用了那只能等了)。因?yàn)榫€程對(duì)進(jìn)程空間各項(xiàng)資源的共享,所以這種模式實(shí)現(xiàn)起來(lái)很方便。關(guān)鍵是,如果某個(gè)線程死掉了,但是沒(méi)有在死之前到記錄薄那里將那個(gè)占有記號(hào)給取消掉,那么其他的線程永遠(yuǎn)也無(wú)法使用該工具。
所以線程,在可預(yù)見(jiàn)性退出或者不可預(yù)見(jiàn)性退出的之前需要從分考慮資源釋放問(wèn)題。
當(dāng)然,除了資源釋放,我們還有很多其他的工作,需要在退出之前做的。這個(gè)動(dòng)作類似于進(jìn)程的atexit定義的動(dòng)作。在線程中,我們將那些要求在線程退出之前的動(dòng)作成為清理工作。
Void??Pthread_cleanup_push( void (* routine)??(void *),??void??*arg);
Void??pthread_cleanup_pop (int execute);
在線程中使用這兩個(gè)函數(shù)來(lái)做有關(guān)的清理工作。
?
這中機(jī)制設(shè)計(jì)起來(lái)非常巧妙,在線程函數(shù)中,你什么時(shí)候申請(qǐng)了一個(gè)獨(dú)占資源,你都使用Pthread_cleanup_push函數(shù)注冊(cè)一個(gè)清理函數(shù),該函數(shù)被壓入棧中。也就是說(shuō),你申請(qǐng)了多少資源(或者其他的事情),你希望在該線程結(jié)束之前被釋放(或者其他動(dòng)作)。有可能有許多的動(dòng)作被壓入棧中。
在結(jié)束之前,通過(guò)pthread_cleanup_push逐個(gè)出棧,并執(zhí)行之前設(shè)定好的函數(shù),來(lái)進(jìn)行清理工作。
?
之所以選擇棧這個(gè)先入后出的結(jié)構(gòu),大家想想就明白了。就像為什么析構(gòu)函數(shù)是與構(gòu)造函數(shù)反方向釋放空間一樣。在申請(qǐng)資源的時(shí)候,只可能后面的資源(變量),依賴前面的資源。所以釋放的時(shí)候按照反的方向釋放才是安全的。
?
這兩個(gè)函數(shù)的具體用法,就是在線程函數(shù)中,在需要注冊(cè)清理函數(shù)的地方調(diào)用pthread_cleanup_push函數(shù)。在最后的結(jié)束的地方,逐個(gè)使用pthread_cleanup_push彈出函數(shù)執(zhí)行。程序可能執(zhí)行不到pthread_cleanup_push那么地方,但是系統(tǒng)規(guī)定,只要線程終止(不管是正常終止,還被強(qiáng)行終止),都會(huì)執(zhí)行這個(gè)東西。
?
這個(gè)時(shí)候你就會(huì)發(fā)現(xiàn)這種清理機(jī)制的巧妙之處了。線程不管執(zhí)行到那個(gè)地方(可能還有些pthread_cleanup_push函數(shù)都沒(méi)有執(zhí)行),都可以終止,這個(gè)時(shí)候調(diào)用那些清理函數(shù),就是合理的。如果使用那種類似于atexit的方式,寫(xiě)死了在線程結(jié)束前要做的事情,那么有時(shí)候我資源都沒(méi)有申請(qǐng)(沒(méi)有執(zhí)行到那個(gè)pthread_cleanup_push那個(gè)地方就被終止了),你卻要調(diào)用函數(shù)來(lái)釋放,是不合適的。而線程使用這種方式,保證了申請(qǐng)了才被釋放的機(jī)制。
?
取消線程
線程外部發(fā)出取消的命令,是以信號(hào)的形式發(fā)送給該線程的,也就是說(shuō)并不是那種直接控制的方式,因?yàn)榫€程已經(jīng)是一個(gè)獨(dú)立的執(zhí)行體,所以它有權(quán)地決定是否以及如何聽(tīng)取你的取消命令。
Extern int thread_cancel??(pthread_t??__cancelthread);
Extern int pthread_setcancelstate(int __state, int *??__oldstate);
連個(gè)state值,分別是:
PTHREAD_CANCEL_DISABLE
PTHREAD_CANCEL_ENABLE
Extern int pthread_setcanceltype(int __type, int *??__oldtype);
兩個(gè)type的有效值為:
PTHREAD_CANCEL_ASYNCHRONOUS
PTHREAD_CANCEL_DEFERRED
?
?
線程私有數(shù)據(jù)(tsd)
有時(shí)候,我們希望執(zhí)行體之間共享、共用某個(gè)數(shù)據(jù),通過(guò)兩個(gè)執(zhí)行體對(duì)一個(gè)變量進(jìn)行修改來(lái)達(dá)到通信的效果。(其實(shí)有一種方式在執(zhí)行體之間傳遞數(shù)據(jù)就是使用參數(shù),但是要實(shí)現(xiàn)兩個(gè)執(zhí)行體之間的這種通信效果是很不方便的)
最好的方法就是使用全局變量。
當(dāng)然全局變量是一種相對(duì)的概念,只要兩個(gè)執(zhí)行體都認(rèn)識(shí)該全局變量,應(yīng)該就算是全局變量。
有了這個(gè)全局變量,我能就可以在不同的函數(shù)中實(shí)現(xiàn)通信,尤其是在這些函數(shù)已經(jīng)獨(dú)立出來(lái)(使用線程)。
例如,對(duì)進(jìn)程占有的某個(gè)資源的占用,我們使用一個(gè)全局變量來(lái)指示還有多少可以用的,每次新建一個(gè)線程,可以實(shí)施對(duì)該資源變量的修改來(lái)表達(dá)它的數(shù)量的增減。
對(duì),這種進(jìn)程線程共享機(jī)制是很有用的。
?
但是,有時(shí)候,我希望有些東西獨(dú)屬于某個(gè)線程,并且可以跨越線程的幾個(gè)函數(shù)。什么意思呢?進(jìn)程中的code可以有很多的函數(shù),而線程是從某個(gè)函數(shù)進(jìn)入的,并且這個(gè)函數(shù)有可能訪問(wèn)其他的函數(shù)。現(xiàn)在對(duì)于同一個(gè)函數(shù)fun1,它內(nèi)部調(diào)用了fun2.同時(shí)兩個(gè)函數(shù)都操作了一個(gè)全局變量n。現(xiàn)在我從fun1函數(shù)這里開(kāi)始產(chǎn)生出多個(gè)線程,每個(gè)線程都對(duì)這個(gè)變量n獨(dú)有,這就是說(shuō)這個(gè)線程對(duì)變量n的更改只限于該線程內(nèi)部的那些函數(shù),與其他線程無(wú)關(guān)。這就與全局變量不同,全局變量沒(méi)有線程的差別,只要被誰(shuí)改了,就被改了。
也許你會(huì)想到一個(gè)方法,使用參數(shù)值傳遞,將全局變量從fun1傳遞進(jìn)入,但是這樣會(huì)很不方便,因?yàn)樵谀硞€(gè)一個(gè)線程中,可能有很多的函數(shù)都會(huì)使用該參數(shù),這就是回到了前面我們討論的如何使用一個(gè)變量在各個(gè)執(zhí)行體之間流轉(zhuǎn)的問(wèn)題。使用的就是全局變量。
于是我們現(xiàn)在需要的就是一個(gè)類似于全局變量,但是有不是全局變量的東西,它屬于一個(gè)線程內(nèi)部的全局變量。
這就是我們要學(xué)到的東西——線程私有數(shù)據(jù)(TSD)
Pthread_key_t??key;
在編寫(xiě)代碼時(shí)使用這樣一個(gè)語(yǔ)句在程序中定義一個(gè)類似于全局變量的東西。
在每個(gè)函數(shù)內(nèi)部使用函數(shù):
Int pthread_key_create(pthread_key_t??* key, void ( *destr_function ) (void *));
這句話的作用就是,在某個(gè)線程中,執(zhí)行這個(gè)函數(shù),將key這個(gè)全局變量注入數(shù)據(jù),這個(gè)時(shí)候,你要注意這不是普通的在線程內(nèi)部
Key=10;
這個(gè)函數(shù),會(huì)在該線程的范圍內(nèi),開(kāi)閉一個(gè)獨(dú)立的空間來(lái)保存key的內(nèi)容。在該線程的范圍內(nèi),該名字(key)所引用的地址都是那個(gè)地方。
不管哪個(gè)線程調(diào)用這個(gè)函數(shù),都會(huì)獨(dú)立在線程范圍內(nèi)開(kāi)辟那個(gè)空間。名字使用key。
?
有些人會(huì)說(shuō),那這樣與我在該函數(shù)內(nèi)部從新定義一個(gè)key,然后使用有什么區(qū)別呢?有的,由于這個(gè)可以仍然扮演全局變量的角色。
當(dāng)我們編寫(xiě)代碼的時(shí)候,我們并不區(qū)分線程,我們也不知道某個(gè)函數(shù)會(huì)被喚起多少個(gè)線程,但是不同的函數(shù)之間通暢的交流使用全局變量就是一個(gè)很好的方法,又由于全局變量不能保證線程之間的獨(dú)立。而TSD,在編碼的時(shí)候保持著全局變量的樣子和作用。在實(shí)際跑的時(shí)候,卻是一個(gè)與線程相關(guān)的全局變量。
?
當(dāng)然了,Pthread_key_t是一個(gè)特殊的全局變量。
讀寫(xiě)那個(gè)線程全局變量的需要使用特殊的函數(shù)。
Int pthread_setspecific (pthread_key_t key, const void * pointer);
Void * pthread_getspecific(pthread_key _t key);
所以,有關(guān)線程私有數(shù)據(jù)本來(lái)就是一個(gè)比較復(fù)雜的問(wèn)題,它所表達(dá)的就是一個(gè)同名不同地址的全局變量。也就是全局同名,各線程中不同地址。
其實(shí)這個(gè)東西還是滿難理解的。如果是一個(gè)應(yīng)用的技巧,也就是說(shuō)不涉及的內(nèi)核的特殊支持。那么可以這樣理解。
首先,定義個(gè)全局變量pthread_key_t??key?這樣的結(jié)構(gòu)。
該結(jié)構(gòu)自然可以被然后一個(gè)線程使用,該結(jié)構(gòu)內(nèi)部有一個(gè)這樣的列表。
List——>pointer>
其中,tid是當(dāng)前線程的線程id,pointer是某個(gè)地址為void *?類型。
使用Pthread_key_create()函數(shù)意在創(chuàng)建一個(gè)這樣的結(jié)構(gòu),開(kāi)始的那個(gè)全局變量很可能是一個(gè)類似于空殼的東西,似乎就是一個(gè)指針而已。只用通過(guò)這個(gè)函數(shù),才能從系統(tǒng)中得到一個(gè)這樣的結(jié)果,并且順便,注冊(cè)一個(gè)函數(shù),用于每個(gè)線程在退出的時(shí)候釋放與key關(guān)聯(lián)的那個(gè)空間。
Pthread_setspecific()函數(shù),將某個(gè)地址注冊(cè)到那個(gè)key結(jié)構(gòu)中,使用tid作為標(biāo)示。
Pthread_getspecific(),將key結(jié)構(gòu)中當(dāng)前tid關(guān)聯(lián)的指針地址返回。
內(nèi)存空間如下:
?
在某個(gè)線程中,只需要通過(guò)set/getsepcific函數(shù),不管在那個(gè)函數(shù)內(nèi)部,在一個(gè)線程內(nèi)部從key那個(gè)結(jié)構(gòu)得到的指針永遠(yuǎn)是一致的。不過(guò)想來(lái),這繞得也太大彎了。
線程屬性管理
前面介紹了線程基本的概念以及有關(guān)創(chuàng)建、退出、等待等等基本的內(nèi)容,這一節(jié)主要介紹線程的屬性控制管理。
屬性是一件東西的內(nèi)在內(nèi)容,作為一個(gè)獨(dú)立的執(zhí)行體,線程需要獨(dú)占一些東西以支持它的獨(dú)立執(zhí)行。(雖然我們一直強(qiáng)調(diào)線程共享進(jìn)程的資源)。
1)?程序計(jì)數(shù)器,由于線程是被拆分了的進(jìn)程執(zhí)行體,cpu中控制指令執(zhí)行的東西必須被各個(gè)線程所獨(dú)有。這個(gè)事執(zhí)行環(huán)境的其中一個(gè)。
2)?一組寄存器,與程序計(jì)數(shù)器一起構(gòu)成了線程執(zhí)行基本環(huán)境。這兩個(gè)屬性是程序員所無(wú)法控制,也不應(yīng)該被控制,就應(yīng)該被透明化的東西。
3)?棧,前面分析了,棧是一個(gè)程序執(zhí)行的依賴結(jié)構(gòu),線程具有一定的獨(dú)立性,需要一個(gè)屬于自己的獨(dú)立棧來(lái)安排它的執(zhí)行執(zhí)行過(guò)程。這棧中的內(nèi)容由用戶編寫(xiě)的函數(shù)大體決定。
4)?線程信號(hào)掩碼,設(shè)置每個(gè)線程的阻塞信號(hào)。
5)?線程局部變量,存在于棧中
6)?線程私有數(shù)據(jù)(tsd),是一種線程級(jí)別的全局變量的應(yīng)用(需內(nèi)核支持)。
我們將線程相關(guān)的東西分為幾類,分別是,基本控制管理(創(chuàng)建、退出等等),內(nèi)容基本管理(函數(shù)編寫(xiě)時(shí)確定,包括線程是有數(shù)據(jù)),屬性管理(有關(guān)一個(gè)線程的棧大小、調(diào)度策略與參數(shù)、能否被等待等屬性,這些屬性影響著線程運(yùn)行),以及程序員無(wú)法觸及的內(nèi)容(程序計(jì)數(shù)器、寄存器等)。
POSIX給操作系統(tǒng)提供的管理線程屬性的結(jié)構(gòu)體:
Typedefstruct??__pthread_attr_s
{
?????????Int???__detachstate;???//是否可被等待默認(rèn)PTHREAD_CREATE_JOINABLE
?????????Int??__schedpolicy;???//調(diào)用策略默認(rèn)?SHED_OTHRE.
?????????struct??__sched_param???__schedparm;??//調(diào)用策略參數(shù)默認(rèn)為優(yōu)先級(jí)0
?????????int??__inheritsched;??//是否繼承創(chuàng)建者的調(diào)度策略
?????????int???__scope;???//爭(zhēng)用范圍默認(rèn)PTHREAD_SCOPE_PROCESS
?????????siee_t???__guardsize;???//棧保護(hù)區(qū)大小
?????????int????__stackaddr_set;???//
?????????void *??__stackaddr;??//棧起始地址,默認(rèn)為NULL,系統(tǒng)自行分配
?????????size_t??__stacksize;??//棧大小,默認(rèn)為0,系統(tǒng)默認(rèn)棧大小
}pthread_attr_t;
上面是系統(tǒng)POSIX提供給我們管理線程屬性的對(duì)象結(jié)構(gòu),按理說(shuō)我們能夠直接通過(guò)賦值控制它,但是,POSIX提供了一系列的操作函數(shù)來(lái)控制它。原因很有多的,關(guān)鍵是可以通過(guò)操作函數(shù)的控制來(lái)控制屬性設(shè)置的合理性。
注意,線程屬性對(duì)象與線程有一定的獨(dú)立性,這個(gè)結(jié)構(gòu)本身并不屬于任何線程,只是當(dāng)我們用于創(chuàng)建新的線程的時(shí)候,調(diào)用的那個(gè)pthread_create函數(shù),需要給予的一個(gè)屬性參數(shù),如果給出的是NULL那就是使用系統(tǒng)默認(rèn)的屬性參數(shù)。
所以一般的過(guò)程是,在程序開(kāi)始創(chuàng)建線程之前,系統(tǒng)先定義好這個(gè)線程屬性對(duì)象。主要的函數(shù)有:
(這些函數(shù),有一個(gè)固定的格式,函數(shù)名以pthread_attr_開(kāi)頭,返回值,大部分尊崇UNIX的管理int類型,0表示成功,-1表示失敗,不管是設(shè)置還是要獲取,都在函數(shù)參數(shù)列表中表現(xiàn),第一個(gè)參數(shù)往往是一個(gè)線程參數(shù)對(duì)象的引用(指針),第二個(gè)參數(shù)是要設(shè)置的值(使用值傳遞),或者是要獲取(注入的屬性值),使用引用(指針傳遞),?并且,通常復(fù)雜參數(shù)也使用引用(指針),以防止復(fù)制該參數(shù))
| 函數(shù)原型 | 說(shuō)明 |
| Extern int??pthread_attr_init( pthread_attr_t * __attr) | 按照系統(tǒng)默認(rèn)值初始化該線程屬性結(jié)構(gòu) |
| Extern int pthread_attr_destroy( pthread_attr_t * attr) | 銷毀已初始化的 |
| Extern int pthread_attr_setdetachstate( pthread_attr_t??*??__attr,??int??__detachstate) | 設(shè)置可被等待屬性 |
| Extern int pthread_attr_getdatachstate( Pthread_attr_t * __attr, int??*??__detachstate) | 獲得可被等待狀態(tài) |
| Extern int??pthread_attr_setstacksize(pthread_attr_t * __attr, size_t??__stacksize) | 設(shè)置線程棧的大小 |
| Extern int pthread_attr_getstacksize(pthread_attr_t * __attr,size_t??*??__restrict??__stacksize) | 獲得線程棧的大小 |
| Extern int pthread_attr_setstackaddr(pthread_attr_t??* __attr, void *??__stackaddr) | 設(shè)置線程棧的起始地址(一般不能設(shè)置,默認(rèn)值為NULL,表示讓系統(tǒng)決定,如果設(shè)置了就只能創(chuàng)建一個(gè)線程了,因?yàn)楹茈y想象兩個(gè)線程共用一個(gè)棧,就像兩個(gè)進(jìn)程共用一個(gè)棧那樣不合理) |
| Extern int pthread_attr_getstackaddr(pthread_attr_t * __attr, void ** __restrict??__stackaddr) | 獲得線程棧的起始地址 |
| Extern int pthread_attr_setguardsize(pthread_attr_t * __attr, size_t??__guardsize | 設(shè)置棧保護(hù)區(qū)大小(棧保護(hù)區(qū)設(shè)置需要多加考慮,不假思索的設(shè)置往往只會(huì)浪費(fèi)內(nèi)容空間) |
| Extern int pthread_attr_getguardisze(pthread_attr_t *?__attr, size_t??* __guardsize) | 獲取棧保護(hù)區(qū)大小 |
| Extern??int pthread_attr_setinheritsched(pthread_attr_t??* __attr, int??__inherit) | 設(shè)置是否從創(chuàng)建者那里繼承調(diào)度策略和關(guān)聯(lián)屬性。 PTHREAD_INHERIT_SCHED PTHREAD_EXPLICITY_SCHED(默認(rèn)) |
| Extern int pthread_attr_getinheritsched(__constpthread_attr_t *?__restrict??__attr, int *??__inherit) ? | 獲取是否繼承調(diào)度策略。 |
| Extern??int pthread_attr_setschedpolicy( pthread_attr_t * __attr, int __policy) | 設(shè)置調(diào)度策略 #define SHCED_OTHER??0//默認(rèn) #define SHCED_FIFO???1 #define??SHCED_RR??2//時(shí)間輪轉(zhuǎn) |
| Extern int pthread _attr_getschedpolicy(__constpthread_attr_t??* __restrict???__attr, int * __policy) | 獲取調(diào)度策略 |
| Extern int pthread_attr_setschedparam(pthread_attr_t *??__attr,??__conststructsched_param??*??_restrict??__param) | 設(shè)置調(diào)度參數(shù) Structsched_param { Int???__sched_priority; } |
| Extern int pthread_attr_getschedparam(_constpthread_attr_t * __restrict??__attr,??structsched_param??*??__restrict | 獲取調(diào)度策略 |
| ? | ? |
?
上面列出了,通過(guò)管理線程屬性對(duì)象來(lái)在線程被創(chuàng)建時(shí)的管理該線程屬性。
在線程已經(jīng)執(zhí)行的時(shí)候,我們時(shí)候能夠在內(nèi)容內(nèi)部、或者是外部得到、設(shè)置相關(guān)屬性呢?當(dāng)然是可以的。當(dāng)然這些屬性有是在線程屬性對(duì)象中的,也有不在線程屬性對(duì)象中的,例如能否被取消等設(shè)置。
1)?Extern pthread_t pthread_self (void)?獲取當(dāng)前線程id(線程內(nèi)部)
2)?Extern int pthread_setschedprio(pthread_t??__target_thread,??int??__prio);(線程外部)
3)?Extern int pthread_setschedparam( pthread_t??__target_thread,??int??__policy,?structsched_param??*???__param);(線程外部)
4)?Extern int pthread_getschedparam(pthread_t??__target__thread, int??*__policy,?structsched_parm??*??_-param);?(線程外部)
5)?Extern int pthread_detach (pthread_t??__t);??(線程外部,改變線程是否能被等待)
6)?Extern int pthread_setcancelstate(int __state, int *??__oldstate);?(線程內(nèi)部設(shè)置能否被取消)
7)?Extern int pthread_setcanceltype(int __type, int *??__oldtype);??(線程內(nèi)部設(shè)置被取消的類型)
?
總的來(lái)說(shuō),設(shè)置獲取一個(gè)線程的相關(guān)屬性,可以通過(guò)線程屬性對(duì)象在創(chuàng)建的時(shí)候設(shè)置的方式、也可以直接對(duì)已存在的線程進(jìn)行設(shè)置,兩者之間有交集。
線程間通信
作為一個(gè)執(zhí)行體,線程間與進(jìn)程間是一樣的,同樣需要通信、同步、異步。不過(guò),線程間與進(jìn)程間有許多的不同。線程共享一個(gè)進(jìn)程的空間(當(dāng)然自己也是保留獨(dú)有的東西的),我們前面分析過(guò),線程就是將一個(gè)順序執(zhí)行的可以執(zhí)行文件,異步的執(zhí)行起來(lái),在一個(gè)進(jìn)程空間中,線程遵守著進(jìn)程code中的某個(gè)一段的規(guī)范,自行執(zhí)行。
由于它們繼承資源的共享,所以,他們之間的通信可以很好的利用進(jìn)程內(nèi)部的共有事物進(jìn)行相關(guān)的同步、異步。雖然有些時(shí)候仍然需要操作系統(tǒng)內(nèi)核的支持,但是就是由于線程共享進(jìn)程資源這個(gè)特點(diǎn),是線程之間的通信非常的輕巧、方便。這是通常選擇多線程而不是多進(jìn)程軟件方式的一個(gè)重要原因。
進(jìn)程間通信的方法主要有:
·?????????互斥鎖:這是線程通信的基本原理和思想,保證同一時(shí)刻只能有一個(gè)線程訪問(wèn)某個(gè)資源。
·?????????條件變量:配合互斥鎖,實(shí)現(xiàn)較好的資源互斥訪問(wèn)策略。
·?????????讀寫(xiě)鎖:更為豐富的互斥鎖機(jī)制,實(shí)現(xiàn)對(duì)資源的讀寫(xiě)區(qū)別資源互斥訪問(wèn)策略。
·?????????信號(hào):進(jìn)程信號(hào)在線程中的應(yīng)用。
我們知道,當(dāng)引入了線程的概念,就一個(gè)進(jìn)程而言,它只是一個(gè)資源占有的實(shí)體而已,即使該進(jìn)程只有一個(gè)線程,它也只是一個(gè)線程。當(dāng)然對(duì)于進(jìn)程間來(lái)說(shuō),是沒(méi)有線程的概念的。但是進(jìn)程間的通信,又必須由進(jìn)程內(nèi)部某個(gè)線程來(lái)支持。這就是出現(xiàn)了一個(gè)很奇怪的現(xiàn)象。進(jìn)程間是無(wú)線程的概念的,但是進(jìn)程間的通信卻是有線程來(lái)完成的。(當(dāng)然其實(shí)糾結(jié)這個(gè)問(wèn)題基本毫無(wú)意義,一個(gè)手術(shù)室是一個(gè)進(jìn)程,對(duì)外顯示為一個(gè)手術(shù)室,而不是若干個(gè)人。如果手術(shù)室中的人表示不同的線程,那么手術(shù)室之間的通信仍然是兩個(gè)手術(shù)室內(nèi)部的人做的。只是對(duì)彼此而言,大家都認(rèn)為是和手術(shù)室通信)。
信號(hào)
就信號(hào)這個(gè)概念來(lái)說(shuō),進(jìn)程(當(dāng)時(shí)沒(méi)有提出線程的概念的時(shí)候)可以給別人發(fā)信號(hào)(kill),可以給自己發(fā)信號(hào)(raise, alarm),來(lái)實(shí)現(xiàn)進(jìn)程間、以及進(jìn)程本身的異步執(zhí)行。
引入線程概念之后,我們就知道,我們所的種種都是使用那個(gè)線程來(lái)完成的,所以說(shuō),那些有關(guān)進(jìn)程的相關(guān)操作(例如信號(hào))完全是在線程中可用的。例如,在一個(gè)進(jìn)程中的如何線程里,你都可以使用kill函數(shù)向別的進(jìn)程發(fā)出信號(hào),你也可以使用raise給自己發(fā)出信號(hào),在進(jìn)程中的無(wú)論哪個(gè)線程安裝了該信號(hào),就會(huì)執(zhí)行。
引入線程后,在加入一些有關(guān)線程特有的信號(hào)操作,我們知道其實(shí)進(jìn)程的信號(hào)操作,完全就是給線程用的,但是線程無(wú)關(guān)的。新加入的信號(hào)操作可以實(shí)現(xiàn)線程之間相互發(fā)信號(hào),并且設(shè)置進(jìn)程內(nèi)部不同線程的信號(hào)掩碼。
這兩個(gè)操作都是針對(duì)線程來(lái)實(shí)現(xiàn)。
Extern int pthread_kill (pthread_t??_threadid, int??__signo);
Extern??intpthread_sigmask (int??__how,??__const??__sigset_t??*??__restrict??__newmask,
?????????????????????????????????????__sigest_t*??__restrict??__oldmask);
這個(gè)pthread_sigmask函數(shù)與pthread_sigprocmask函數(shù)對(duì)應(yīng),后者是來(lái)為整個(gè)進(jìn)程進(jìn)行屏蔽的,它的影響會(huì)到達(dá)每個(gè)線程,而前者則是為各個(gè)線程設(shè)置。
所以,信號(hào),并不是獨(dú)屬于進(jìn)程間通信的,它除了能夠表達(dá)在進(jìn)程間進(jìn)行信號(hào)的傳遞,它還能夠在線程間進(jìn)行傳遞。至于其他的進(jìn)程間通信機(jī)制,他們雖然仍然是通過(guò)線程操作、管理(引入線程后進(jìn)程已經(jīng)不再是一個(gè)執(zhí)行體的代表了),但是仍然只是設(shè)計(jì)進(jìn)程間的通信,無(wú)法將他們應(yīng)用到線程間通信。
基于共有資源(變量)的線程間的同步、異步通信
序
我們總說(shuō),線程共享進(jìn)程的資源,其實(shí)這里的共享,最大的部分還是對(duì)全局變量的共享。要理解,線程是某個(gè)可執(zhí)行文件中的某個(gè)函數(shù)跳出來(lái)獨(dú)立執(zhí)行而已,它并不脫離它所屬的環(huán)境,也就是說(shuō),要判斷某個(gè)線程是否對(duì)資源(變量、空間、資源等,其實(shí)在程序的層面上,資源就是變量或者是變量指向的某個(gè)實(shí)體)具有訪問(wèn)和使用權(quán)限(或者說(shuō)該資源對(duì)該線程是否可見(jiàn)),只要看程序就可以了,該線程所承載的函數(shù)能見(jiàn)到的資源,該線程就能夠訪問(wèn)。
所以你會(huì)看到,線程間的通信手段(互斥鎖、條件變量、讀寫(xiě)鎖)都是基于一個(gè)全局變量,不屬于任何函數(shù)的變量,是某個(gè)高層函數(shù)中的變量。這樣可以達(dá)到他們能夠看見(jiàn)的效果。
線程對(duì)進(jìn)程資源的共享,表達(dá)出來(lái)的意思是“可見(jiàn)”,但是不同的線程是否能夠隨意的操作(讀寫(xiě))它可見(jiàn)的公共資源,就是線程間同步所要做的東西。
事實(shí)上,線程間數(shù)據(jù)通信還不是很重要,因?yàn)榫€程間共享率很高,不像進(jìn)程間。但是這種共享率過(guò)高的情況導(dǎo)致了另外一個(gè)問(wèn)題,那就是對(duì)某個(gè)資源的訪問(wèn),需要協(xié)調(diào)不同的線程的過(guò)程,要不然會(huì)出現(xiàn)混亂,也就是說(shuō)需要管理線程對(duì)某個(gè)公共資源的操作,比如,同一時(shí)間只能有一個(gè)線程操作它(互斥鎖),或者是可以有很多線程同時(shí)讀,但只能有一個(gè)線程同時(shí)寫(xiě)(讀寫(xiě)鎖)。這就是同步,不要太糾結(jié)“同步”的同字,同步的意思是協(xié)調(diào),規(guī)范不同執(zhí)行的執(zhí)行。
值得注意的是,在線程領(lǐng)域中,一方面,我們同步是為了線程之間能夠按照某種先后過(guò)程調(diào)度來(lái)訪問(wèn)某個(gè)公共資源(變量);另一方面,我們實(shí)現(xiàn)這種同步的方式基本上也是使用公共資源(變量)的特性來(lái)實(shí)現(xiàn)的。
所以,線程中同步機(jī)制,起點(diǎn)和落點(diǎn)是線程的共享進(jìn)程資源特性,我們使用那些不注重訪問(wèn)策略的共享變量來(lái)控制,那些需要控制的共享變量。
互斥鎖
互斥鎖,是以排它的方式控制共享數(shù)據(jù)被線程訪問(wèn)的過(guò)程,這種模式是通過(guò)控制線程中訪問(wèn)該共享數(shù)據(jù)的代碼來(lái)控制他們的執(zhí)行,而不是直接控制該共享數(shù)據(jù)。意思就是,線程中(函數(shù)中)的某一段代碼,必須要滿足某個(gè)條件才能繼續(xù)往下執(zhí)行,否則阻塞在那里,直到條件達(dá)到位置。
它的效果類似于:
Boolmutex;
線程1:
While(!Mutex);
Mutext=false;
……
訪問(wèn)公共資源代碼
……
Mutex=ture;
?
線程2:
與線程1類似。
兩個(gè)線程,使用公共數(shù)據(jù)mutex來(lái)控制對(duì)某個(gè)重要資源的互斥訪問(wèn),線程1中,一定要等到mutex的值為true時(shí),才能突破while循環(huán),進(jìn)入下一步,否則會(huì)一直在那里執(zhí)行著,直到等到線程二,執(zhí)行完將mutex編程true。
這種模擬雖然意思到了,但是真正的linux互斥鎖,是阻塞策略。而不是循環(huán)問(wèn)詢。自然是阻塞策略的好,當(dāng)線程1發(fā)現(xiàn)mutex不可用,于是就阻塞起來(lái),知道它可用的時(shí)候系統(tǒng)在發(fā)送信息喚醒該線程,并是該線程獲得該共享資源的控制權(quán)。這里關(guān)鍵的一個(gè)就是如何喚醒被阻塞的線程,這是需要內(nèi)核支持的。當(dāng)然為了程序員簡(jiǎn)單,那些復(fù)雜的過(guò)程,都已經(jīng)被包裝好了。主要的互斥鎖語(yǔ)句如下:
1)定義互斥鎖:
Pthread_mutex_t??lock;//必須是一個(gè)線程公共區(qū)(一般是一個(gè)全局變量)
2)初始化互斥鎖:
Extern??int pthread_mutex_init( pthread_mutext_t *??__mutex,??__constpthread_mutexattr_t *__mutexattr);
類似與使用線程屬性對(duì)象創(chuàng)建線程一樣,這里使用pthread_mutexattr_t線程互斥屬性的引用來(lái)初始化該互斥鎖,如果是NULL,則使用系統(tǒng)默認(rèn)的方法。
還可以直接靜態(tài)初始化互斥鎖:
Pthread_mutex_tmp=PTHREAD_MUTEX_INITIALIZER;
這樣就免除了調(diào)用初始化函數(shù),其中PTHREAD_MUTEX_INITIALIZER是這么被定義的:
#define PTHREAD_MUTEX_INITIALIZER??{ {0,?}},從這里我們依稀可以理解mutex這個(gè)結(jié)構(gòu)的大體內(nèi)容了。
注意:我們發(fā)現(xiàn),在linux上的C編程中,總是有這樣的過(guò)程,先定義,然后初始化,我們?cè)趺纯炊加X(jué)得怪怪的,那是因?yàn)?#xff0c;如果是面向?qū)ο笳Z(yǔ)言,類似于結(jié)構(gòu)體這樣的符合對(duì)象,是有構(gòu)造函數(shù)的,而構(gòu)造函數(shù)的作用就是分配并初始化符合結(jié)構(gòu)。但是C中沒(méi)有,所以,一個(gè)復(fù)合結(jié)構(gòu)被定義后需要初始化,而初始化,如果直接使用元素賦值的方法那將是非常恐怖和不便的,所以C中使用的是定義初始化方法,這個(gè)方法的存在意義就是作為構(gòu)造函數(shù)而存在的,只不過(guò)需要程序員人為定義。
3)銷毀互斥鎖
Extern??int pthread_mutex_destroy ( pthread_mutex_t??*??__mutex)
這里有一個(gè)對(duì)應(yīng)關(guān)系,在C++中,析構(gòu)函數(shù)負(fù)責(zé)對(duì)對(duì)象生命周期的收尾工作,析構(gòu)函數(shù)是在對(duì)象生命周期接收之前被調(diào)用的。
所以我們認(rèn)為,構(gòu)造函數(shù)或析構(gòu)函數(shù)的功能是不應(yīng)該包括分配空間和釋放空間,兩個(gè)函數(shù)只是在對(duì)象分配空間之后、以及釋放空間之前系統(tǒng)默認(rèn)調(diào)用的函數(shù)。而C里面初始化函數(shù)和銷毀函數(shù)主要就是扮演著這個(gè)角色,所以這樣看兩者是對(duì)應(yīng)的。這里的銷毀動(dòng)作,也并不是釋放互斥鎖的空間,互斥鎖的空間(聲明周期)是由系統(tǒng)控制的,如果是全局變量,那么它全局存在,不會(huì)因?yàn)檎{(diào)用銷毀函數(shù)而空間被釋放了。不過(guò)可以這么理解,調(diào)用了銷毀函數(shù),該互斥鎖會(huì)被清零,開(kāi)始初始化以及后來(lái)的賦值的結(jié)果都被清零。
注意:這一點(diǎn)又在一次的證明了語(yǔ)言是沒(méi)有能力大小之分的。
4)申請(qǐng)互斥鎖
Extern??int pthread_mutex_lock (pthread_mutex * __mutex);
上面的阻塞式申請(qǐng),意思是申請(qǐng)不到,當(dāng)前線程就要阻塞,如下是一個(gè)非阻塞申請(qǐng)函數(shù):
Extern int pthread_mutex_trylock(pthread_mutex??* __mutex);
非阻塞申請(qǐng)的意思是,申請(qǐng)一下,能申請(qǐng)就申請(qǐng),不能申請(qǐng)我就走,不管了,干別的去。
5)釋放互斥鎖
Extern??int pthread_mutex_unlock (pthread_mutex_t *??_mutext);
?
條件變量
介紹了互斥鎖,感覺(jué)所有的需要在線程間進(jìn)行排它訪問(wèn)控制的事情,互斥鎖都能夠做到,但是互斥鎖仍然有些地方是無(wú)法做到的。
書(shū)上講了一個(gè)例子非常貼切,不過(guò)我們盡量尋找一個(gè)比較一般的場(chǎng)景。
線程A、B,需要排他訪問(wèn)共享數(shù)據(jù)i,A會(huì)不斷的改變i的值,而B需要等到i為某個(gè)值的時(shí)候,才能觸發(fā)一定的動(dòng)作。看起來(lái)似乎沒(méi)有問(wèn)題。但是問(wèn)題出現(xiàn)在,有可能i已經(jīng)達(dá)到B需要的那個(gè)值,但是,當(dāng)時(shí)A、B爭(zhēng)搶i的訪問(wèn)權(quán)限的時(shí)候,A爭(zhēng)到了,并且對(duì)i進(jìn)行了修改,于是后面B就算是再爭(zhēng)搶到了,也無(wú)法在執(zhí)行指定的動(dòng)作了。
有些人說(shuō),我可以建立兩個(gè)線程,做嚴(yán)格控制,讓每次A改變了之后,釋放該共享數(shù)據(jù),一定要留一段時(shí)間,能夠讓B得到控制權(quán)。對(duì)似乎這個(gè)方法合適,但是當(dāng)線程的復(fù)雜都增多,并發(fā)的線程增多時(shí),你就會(huì)發(fā)現(xiàn)這種控制非常困難。
并且,即使通過(guò)比較好的時(shí)間控制,達(dá)到上面的效果,你會(huì)發(fā)現(xiàn)B執(zhí)行它的動(dòng)作只有一次機(jī)會(huì)(等到i為合適的值),但是就因?yàn)檫@個(gè)僅有的機(jī)會(huì),B需要不斷的讀取釋放該數(shù)據(jù),這樣明顯浪費(fèi)了很多的寶貴資源。對(duì)于上面的這個(gè)場(chǎng)景,我們就希望,B不必要每次都去申請(qǐng)釋放該共享數(shù)據(jù),我們希望,當(dāng)i達(dá)到指定值的時(shí)候能夠,主動(dòng)提醒B線程。
這就是條件變量。
這個(gè)“主動(dòng)通知”的好處在哪里?關(guān)鍵是,B線程如果想要知道i是否達(dá)到指定值,就必須每次它被修改的時(shí)候,都要申請(qǐng)鎖,然后查看它,這個(gè)非常浪費(fèi)的動(dòng)作,A線程中如果在i到達(dá)了某個(gè)值的時(shí)候,能夠主動(dòng)通知B線程是多么好的過(guò)程。這是條件變量的最大好處。
條件變量的使用場(chǎng)景:
多線程互斥訪問(wèn)某個(gè)共享數(shù)據(jù),但是,線程即使得到了該共享數(shù)據(jù)的訪問(wèn)權(quán),也需要先判斷是否達(dá)到訪問(wèn)的條件,才進(jìn)行相應(yīng)的操作。所以條件變量是需要和互斥變量同時(shí)使用的,是在簡(jiǎn)單排他訪問(wèn)的基礎(chǔ)上,添加了條件的因素。
(1)??????定義、初始化、銷毀條件變量
Pthread_cond_tcondtion;
Extern??intpthread_cond_init??(pthread_cond_t??*??__restrict??__cond, __constpthread_condattr_t??* __restrict??__cond_attr);
Extern int pthread_cond_destroy (pthread_cond_t??* __cond);
(2)??????通知條件發(fā)生
Extern??intpthread_cond_signal (pthread_cond_t *??__cond);
Extern??intpthread_cond_broadcast(pthread_cond_t * __cond);
兩者的區(qū)別在于,signal只通知第一個(gè)等待該條件線程
(3)??????等待條件方法
Extern int pthread_cond_wait (pthread_cond_t * __restrict??__cond, pthread_mutex_t?*__restrict??__mutex);
extern int pthread_cond_timedwait (pthread_cond_t * __restrict??_cond, pthread_mutex_t?*__restrict??__mutex,??__conststructtimespec??* __restrict??__abstime);
兩者的區(qū)別在于,后者會(huì)在一定時(shí)間范圍內(nèi)等待條件的發(fā)生。
并且,從等待條件方法的參數(shù)可以看出,條件變量在語(yǔ)法上已經(jīng)是和互斥鎖緊密聯(lián)系著的。等待條件發(fā)生的那個(gè)線程,先申請(qǐng)互斥鎖,然后再使用其中一個(gè)函數(shù)申請(qǐng)條件,如果條件不符合,則阻塞,并且默認(rèn)釋放該互斥鎖。等到從阻塞返回時(shí),先申請(qǐng)到互斥鎖。
其實(shí)這里有一個(gè)很奇怪的事情,那就是為什么在使用等待條件的之前還要申請(qǐng)互斥鎖,既然等待條件方法對(duì)互斥鎖,有控制,那么大可以直接一句話完成。
與互斥鎖配合使用
1)?首先,定義、初始化、以及銷毀與互斥鎖是一樣的。
2)?在A線程,抓住一次互斥鎖后,先對(duì)共享變量進(jìn)行操作,然后判斷是否出現(xiàn)特定條件。如果出現(xiàn)了,則首先釋放互斥鎖,然后通知條件發(fā)生。這里要注意,一定要先釋放鎖,因?yàn)?#xff0c;B線程一般就阻塞在等到條件的那個(gè)地方,這個(gè)地方要喚醒,首先需要申請(qǐng)關(guān)聯(lián)互斥鎖。如果沒(méi)有出現(xiàn),則照常釋放。
3)?在B線程中,首先申請(qǐng)互斥鎖,然后,等待條件。
讀寫(xiě)鎖
最后我們來(lái)看看讀寫(xiě)鎖,讀寫(xiě)鎖是對(duì)互斥鎖基本思想的一個(gè)擴(kuò)充,這種擴(kuò)充是面向計(jì)算機(jī)的一個(gè)特定策略哲學(xué)(可能不只是計(jì)算機(jī))。就是一個(gè)共享數(shù)據(jù),可以同時(shí)供多個(gè)執(zhí)行體讀訪問(wèn),但只能同時(shí)供一個(gè)執(zhí)行體寫(xiě)訪問(wèn)。這是就是我們通常所說(shuō)的讀寫(xiě)鎖。讀寫(xiě)鎖與互斥鎖都稱為鎖,大家的基本思想都是一樣的。條件變量基本上,可以稱為互斥鎖上的一次擴(kuò)展而已。
1)?定義、初始化、銷毀讀寫(xiě)鎖
Pthread_rwlock_trwl;
Extern int pthread_rwlock_init (pthread_rwlock_t??*??__restrict??__rwlock, __constpthread_rwlockattr_t * __restrict??_attr);
Extern int pthread_rwlock_destroy (pthread_rwlock_t*??__rwlock);
2)?申請(qǐng)讀鎖
Extern int??pthread_rwlock_rdlock( pthread_rwlock_t * __rwlock);
Extern int pthread_rwlock_tryrdlock(pthread_rwlock_t * __rwlock);
3)?申請(qǐng)寫(xiě)鎖
Extern int pthread_rwlock_wrlock(pthread_rwlock_t * __rwlock);
Extern int pthread_rwlock_trywrlock(pthread_rwlock_t * __rwlock);
4)?解鎖
Extern int pthread_rwlock_unlock(pthread_rwlock_t *??__rwlock);
?
?
總結(jié)
以上是生活随笔為你收集整理的linux 线程管理、同步机制等的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux查看和关闭后台执行程序
- 下一篇: LINUX任务(jobs)详解