APR分析-線程同步篇

APR分析-線程同步篇

在線程同步方面,Posix標(biāo)準(zhǔn)定義了3種同步模型,分別為互斥量、條件變量和讀寫鎖。APR也“淺”封裝了這3種模型,只是在“讀寫鎖”一塊兒還沒有全部完成。

線程同步的源代碼的位置在$(APR_HOME)/locks目錄下,本篇blog著重分析unix子目錄下的thread_mutex.c、thread_rwlock.c和thread_cond.c文件的內(nèi)容,其相應(yīng)頭文件為(APR_HOME)/include/apr_thread_mutex.h、apr_thread_rwlock.h和apr_thread_cond.h。

由于APR的封裝過于“淺顯”,實(shí)際上也并沒有多少值得分析的“靚點(diǎn)”。所以本篇實(shí)際上是在討論線程同步的3種運(yùn)行模型。

一、互斥量

互斥量是線程同步中最基本的同步方式。互斥量用于保護(hù)代碼中的臨界區(qū),以保證在任一時(shí)刻只有一個(gè)線程或進(jìn)程訪問臨界區(qū)。

1、互斥量的初始化

在POSIX Thread中提供兩種互斥量的初始化方式,如下:

(1)靜態(tài)初始化

互斥量首先是一個(gè)變量,Pthread提供預(yù)定義的值來支持互斥量的靜態(tài)初始化。舉例如下:

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

既然是靜態(tài)初始化,那么必然要求上面的mutex變量需要靜態(tài)分配。在APR中并不支持apr_thread_mutex_t的使用預(yù)定值的靜態(tài)初始化(但可以變通的利用下面的方式進(jìn)行靜態(tài)分配的mutex的初始化)。

(2)動(dòng)態(tài)初始化

除了上面的情況,如果mutex變量在堆上或在共享內(nèi)存中分配的話,我們就需要調(diào)用一個(gè)初始化函數(shù)來動(dòng)態(tài)初始化該變量了。在Pthread中的對(duì)應(yīng)接口為pthread_mutex_init。APR封裝了這一接口,我們可以使用下面方式在APR中初始化一個(gè)apr_thread_mutex_t變量。

apr_thread_mutex_t?*mutex?= NULL;

apr_pool_t??*pool?= NULL;

apr_status_t??stat;

stat =apr_pool_create(&pool, NULL);

if (stat !=APR_SUCCESS) {

printf("error in pool %d/n", stat);

} else {

printf("ok in pool/n");

}

stat =apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pool);

if (stat !=APR_SUCCESS) {

printf("error %d in mutex/n", stat);

} else {

printf("ok in mutex/n");

}

2、互斥鎖的軟弱性所在

互斥鎖之軟弱性在于其是一種協(xié)作性鎖,其運(yùn)作時(shí)對(duì)各線程有一定的要求,即“所有要訪問臨界區(qū)的線程必須首先獲取這個(gè)互斥鎖,離開臨界區(qū)后釋放該鎖”,一旦某一線程不遵循該要求,那么這個(gè)互斥鎖就形同虛設(shè)了。如下面的例子:

舉例:我們有兩個(gè)線程,一個(gè)線程A遵循要求,每次訪問臨界區(qū)均先獲取鎖,然后將臨界區(qū)的變量x按偶數(shù)值遞增,另一個(gè)線程B不遵循要求直接修改x值,這樣即使在線程A獲取鎖的情況下仍能修改臨界區(qū)的變量x。

static apr_thread_mutex_t??????*mutex? = NULL;

staticint???????????????????????????????x?????? = 0;

staticapr_thread_t????????????*t1???? = NULL;

staticapr_thread_t????????????*t2???? = NULL;

static void * APR_THREAD_FUNC thread_func1(apr_thread_t *thd, void*data)

{

apr_time_t????? now;

apr_time_exp_t?xt;

while (1) {

apr_thread_mutex_lock(mutex);

now = apr_time_now();

apr_time_exp_lt(&xt, now);

printf("[threadA]: own the lock, time[%02d:%02d:%02d]/n", xt.tm_hour,xt.tm_min,

xt.tm_sec);

printf("[threadA]: x = %d/n", x);

if (x % 2 || x == 0) {

x += 2;

} else {

printf("[threadA]: Warning: x變量值被破壞,現(xiàn)重新修正之/n");

x += 1;

}

apr_thread_mutex_unlock(mutex);

now = apr_time_now();

apr_time_exp_lt(&xt, now);

printf("[threadA]: release the lock, time[%02d:%02d:%02d]/n",xt.tm_hour, xt.tm_min,

xt.tm_sec);

sleep(2);

}

return NULL;

}

static void * APR_THREAD_FUNC thread_func2(apr_thread_t *thd, void*data)

{

apr_time_t????? now;

apr_time_exp_t?xt;

while (1) {

x ++;

now = apr_time_now();

apr_time_exp_lt(&xt, now);

printf("[threadB]: modify the var, time[%02d:%02d:%02d]/n",xt.tm_hour, xt.tm_min,? xt.tm_sec);

sleep(2);

}

return NULL;

}

int main(int argc, const char * const * argv, const char * const*env)

{

apr_app_initialize(&argc, &argv, &env);

apr_status_t stat;

//...

/*

*創(chuàng)建線程

*/

stat =apr_thread_create(&t1, NULL, thread_func1, NULL, pool);

stat =apr_thread_create(&t2, NULL, thread_func2, NULL, pool);

//...

apr_terminate();

return 0;

}

//output

... ...

[threadA]: own the lock, time[10:10:15]

[threadB]: modify the var, time[10:10:15]

[threadA]: x = 10

[threadA]: Warning: x變量值被破壞,現(xiàn)重新修正之

[threadA]: release the lock, time[10:10:15]

當(dāng)然這個(gè)例子不一定很精確的表明threadB在threadA擁有互斥量的時(shí)候修改了x值。

二、條件變量

互斥量一般用于被設(shè)計(jì)被短時(shí)間持有的鎖,一旦我們不能確定等待輸入的時(shí)間時(shí),我們可以使用條件變量來完成同步。我們?cè)?jīng)說過I/O復(fù)用,在我們調(diào)用poll或者select的時(shí)候?qū)嶋H上就是在內(nèi)核與用戶進(jìn)程之間達(dá)成了一個(gè)協(xié)議,即當(dāng)某個(gè)I/O描述符事件發(fā)生的時(shí)候內(nèi)核通知用戶進(jìn)程并且將處于掛起狀態(tài)的用戶進(jìn)程喚醒。而這里我們所說的條件變量讓對(duì)等的線程間達(dá)成協(xié)議,即“某一線程發(fā)現(xiàn)某一條件滿足時(shí)必須發(fā)信號(hào)給阻塞在該條件上的線程,將后者喚醒”。這樣我們就有了兩種角色的線程,分別為

(1)給條件變量發(fā)送信號(hào)的線程

其流程大致為:

{

獲取條件變量關(guān)聯(lián)鎖;

修改條件為真;

調(diào)用apr_thread_cond_signal通知阻塞線程條件滿足了;------(a)

釋放變量關(guān)聯(lián)鎖;

}

(2)在條件變量上等待的線程

其流程大致為:

{

獲取條件變量關(guān)聯(lián)鎖;

while (條件為假) {--------------------- (c)

調(diào)用apr_thread_cond_wait阻塞在條件變量上等待;------(b)

}

修改條件;

釋放變量關(guān)聯(lián)鎖;

}

上面兩個(gè)流程中,理解三點(diǎn)最關(guān)鍵:

a) apr_thread_cond_signal中調(diào)用的pthread_cond_signal保證至少有一個(gè)阻塞在條件變量上的線程恢復(fù);在《Unix網(wǎng)絡(luò)編程Vol2》中也談過這里存在著一個(gè)race。即在發(fā)送cond信號(hào)的同時(shí),該發(fā)送線程仍然持有條件變量關(guān)聯(lián)鎖,那么那個(gè)恢復(fù)線程的apr_thread_cond_wait返回時(shí)仍然拿不到這把鎖就會(huì)再次掛起。這里的這個(gè)race要看各個(gè)平臺(tái)實(shí)現(xiàn)是如何處理的了。

b) apr_thread_cond_wait中調(diào)用的pthread_cond_wait原子的將調(diào)用線程掛起,并釋放其持有的條件變量關(guān)聯(lián)鎖;

c)這里之所以使用while反復(fù)測(cè)試條件,是防止“偽喚醒”的存在,即條件并未滿足就被喚醒。所以無論怎樣,喚醒后我都需要重新測(cè)試一下條件,保證該條件的的確確滿足了。

條件變量在解決“生產(chǎn)者-消費(fèi)者”問題中有很好的應(yīng)用,在我以前的一篇blog中也說過這個(gè)問題。

三、讀寫鎖

前面說過,互斥量把想進(jìn)入臨界區(qū)而又試圖獲取互斥量的所有線程都阻塞住了。讀寫鎖則改進(jìn)了互斥量的這種霸道行為,它區(qū)分讀臨界區(qū)數(shù)據(jù)和修改臨界區(qū)數(shù)據(jù)兩種情況。這樣如果有線程持有讀鎖的話,這時(shí)再有線程想讀臨界區(qū)的數(shù)據(jù)也是可以再獲取讀鎖的。讀鎖和寫鎖的分配規(guī)則在《Unix網(wǎng)絡(luò)編程Vol2》中有詳細(xì)說明,這里不詳述。

四、小結(jié)

三種同步方式如何選擇?場(chǎng)合不同選擇也不同。互斥量在于完全同步的臨界區(qū)訪問;條件變量在解決“生產(chǎn)者-消費(fèi)者”模型問題上有獨(dú)到之處;讀寫鎖則在區(qū)分對(duì)臨界區(qū)讀寫的時(shí)候使用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容