APR分析-線程同步篇

APR分析-線程同步篇

在線程同步方面,Posix標準定義了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的封裝過于“淺顯”,實際上也并沒有多少值得分析的“靚點”。所以本篇實際上是在討論線程同步的3種運行模型。

一、互斥量

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

1、互斥量的初始化

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

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

互斥量首先是一個變量,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)初始化(但可以變通的利用下面的方式進行靜態(tài)分配的mutex的初始化)。

(2)動態(tài)初始化

除了上面的情況,如果mutex變量在堆上或在共享內(nèi)存中分配的話,我們就需要調(diào)用一個初始化函數(shù)來動態(tài)初始化該變量了。在Pthread中的對應(yīng)接口為pthread_mutex_init。APR封裝了這一接口,我們可以使用下面方式在APR中初始化一個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é)作性鎖,其運作時對各線程有一定的要求,即“所有要訪問臨界區(qū)的線程必須首先獲取這個互斥鎖,離開臨界區(qū)后釋放該鎖”,一旦某一線程不遵循該要求,那么這個互斥鎖就形同虛設(shè)了。如下面的例子:

舉例:我們有兩個線程,一個線程A遵循要求,每次訪問臨界區(qū)均先獲取鎖,然后將臨界區(qū)的變量x按偶數(shù)值遞增,另一個線程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]

當然這個例子不一定很精確的表明threadB在threadA擁有互斥量的時候修改了x值。

二、條件變量

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

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

其流程大致為:

{

獲取條件變量關(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)鎖;

}

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

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

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

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

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

三、讀寫鎖

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

四、小結(jié)

三種同步方式如何選擇?場合不同選擇也不同?;コ饬吭谟谕耆降呐R界區(qū)訪問;條件變量在解決“生產(chǎn)者-消費者”模型問題上有獨到之處;讀寫鎖則在區(qū)分對臨界區(qū)讀寫的時候使用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容