Linux線程管理必備:解析互斥量與條件變數的詳解

來源:互聯網
上載者:User

做過稍微大一點項目的人都知道,力求程式的穩定性和調度的方便,使用了大量的線程,幾乎每個模組都有一個專門的線程處理函數。而互斥量與條件變數線上程管理中必不可少,任務間的調度幾乎都是由互斥量與條件變數控制。互斥量的實現與進程中的訊號量(無名訊號量)是類似的,當然,訊號量也可以用於線程,區別在於初始化的時候,其本質都是P/V操作。編譯時間,記得加上-lpthread或-lrt哦。

有關處理序間通訊(訊息佇列)見:處理序間通訊之深入訊息佇列的詳解

一、互斥量

1. 初始化與銷毀:

對於靜態分配的互斥量, 可以初始化為PTHREAD_MUTEX_INITIALIZER等價於pthread_mutex_init(…, NULL))調用pthread_mutex_init。

對於動態分配的互斥量, 在申請記憶體(malloc)之後,通過pthread_mutex_init進行初始化, 並且在釋放記憶體(free)前需要調用pthread_mutex_destroy.

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t*restric attr);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

傳回值:成功則返回0,出錯則返回錯誤編號.

說明:1、如果使用預設的屬性初始化互斥量,只需把attr設為NULL

2、銷毀一個互斥鎖即意味著釋放它所佔用的資源,且要求鎖當前處於開放狀態。由於在Linux中,互斥鎖並不佔用任何資源,因此 LinuxThreads中的pthread_mutex_destroy()除了檢查鎖狀態以外(鎖定狀態則返回EBUSY)沒有其他動作。

2. 互斥操作:

對共用資源的訪問, 要對互斥量進行加鎖,如果互斥量已經上了鎖, 調用線程會阻塞,直到互斥量被解鎖。在完成了對共用資源的訪問後, 要對互斥量進行解鎖。

int pthread_mutex_lock(pthread_mutex_t *mutex); //P操作:請求資源(+1)

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);//V操作:釋放資源(-1)

傳回值:成功則返回0,出錯則返回錯誤編號.

說明:1、想給一個互斥量上鎖,我們調用pthread_mutex_lock。如果mutex已經上鎖,調用的線程將會被阻塞,直至訊號量解鎖。

2、具體說一下trylock函數, 這個函數是非阻塞調用模式,也就是說, 如果互斥量沒被鎖住,trylock函數將把互斥量加鎖, 並獲得對共用資源的存取權限;如果互斥量被鎖住了,trylock函數將不會阻塞等待而直接返回EBUSY, 表示共用資源處於忙狀態。

3、要解鎖一個訊號量,我們調用phtread_mutex_unlock。

3. 死結、同步、與互斥的關係

3.1 死結:

有時,可能需要同時訪問兩個資源。您可能正在使用其中的一個資源,隨後發現還需要另一個資源。如果兩個線程嘗試聲明這兩個資源,但是以不同的順序鎖定與這些資源相關聯的互斥鎖,則會出現問題。例如,如果兩個線程分別鎖定互斥鎖1 和互斥鎖 2,則每個線程嘗試鎖定另一個互斥鎖時,將會出現死結。下面的例子說明了可能的死結情況。

線程 1

線程 2

pthread_mutex_lock(&m1);

pthread_mutex_lock(&m2);

do something……

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);

pthread_mutex_lock(&m2);

pthread_mutex_lock(&m1);

do something……

pthread_mutex_unlock(&m1);

pthread_mutex_unlock(&m2);

3.2 同步:

線程 1

線程 2

pthread_mutex_lock(&m1);

do something……

pthread_mutex_unlock(&m2);

pthread_mutex_lock(&m2);

do something……

pthread_mutex_unlock(&m1);

3.3 互斥:

線程 1

pthread_mutex_lock(&m1);

do something……//臨界區(Critical Section)

pthread_mutex_unlock(&m1);

4. 互斥量之前輩總結

1.對共用資源操作前一定要獲得鎖。

2.完成操作以後一定要釋放鎖。

3.盡量短時間地佔用鎖。

4.如果有多鎖, 如獲得順序是ABC連環扣,釋放順序也應該是ABC。

5.線程錯誤返回時應該釋放它所獲得的鎖。

二、條件變數

1. 建立和登出

條件變數和互斥鎖一樣,都有靜態動態兩種建立方式

a. 靜態方式

靜態方式使用PTHREAD_COND_INITIALIZER常量,如: pthread_cond_t cond = PTHREAD_COND_INITIALIZER

b. 動態方式
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)

使用 cond_attr 指定的屬性初始化條件變數 cond,當 cond_attr為 NULL時,使用預設的屬性。LinuxThreads實現條件變數不支援屬性,因此 cond_attr參數實際被忽略。
c. 登出

int pthread_cond_destroy(pthread_cond_t *cond)

登出一個條件變數需要調用pthread_cond_destroy(),只有在沒有線程在該條件變數上等待的時候才能登出這個條件變數,否則返回EBUSY。因為Linux實現的條件變數沒有分配什麼資源,所以登出動作只包括檢查是否有等待線程。

2. 等待和激發

2.1 等待

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
這個函數是POSIX線程訊號發送系統的核心,也是最難以理解的部分,過程為:解鎖-wait-收到訊號-加鎖-返回。

2.2 設定時間的等待

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, conststruct timespec *abstime)

pthread_cond_timedwait和 pthread_cond_wait一樣,自動解鎖互斥量及等待條件變數,但它還限定了等待時間。如果在 abstime指定的時間內 cond未觸發,互斥量 mutex被重新加鎖,並返回錯誤 ETIMEDOUT。abstime參數指定一個絕對時間,時間原點與 time和 gettimeofday相同:abstime = 0表示 1970 年 1月 1 日 00:00:00 GMT。

2.3 激發

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

激發條件有兩種形式,pthread_cond_signal()啟用一個等待該條件的線程,多個線程阻塞在此條件變數上時,哪一個線程被喚醒是由線程的調度策略所決定的;而pthread_cond_broadcast()則啟用所有等待線程,這些線程被喚醒後將再次競爭相應的互斥鎖。

要注意的是,必須用保護條件變數的互斥鎖來保護啟用函數,否則條件滿足訊號有可能在測試條件和調用pthread_cond_wait()函數之間被發出,從而造成無限制的等待。

三、互斥量與條件變數

互斥量存在的問題:從本質上說互斥量就是一把鎖,互斥量串列執行,能確保每次只有一個線程訪問互斥量是線程程式必需的工具,但它們並非萬能的。例如,如果線程正在輪詢等待共用資料內某個條件出現,那會發生什麼呢?它可以重複對互斥對象鎖定和解鎖,每次都會檢查共用資料結構,以尋找某個值。但這是在浪費時間和資源,而且這種繁忙查詢的效率非常低。同樣,在每次檢查之間讓線程短暫地進入睡眠,比如睡眠3s,但是因此線程代碼就無法最快作出響應。

問題的解決: 條件變數通過允許線程阻塞和等待另一個線程發送訊號的方法彌補了互斥鎖的不足,條件變數常和互斥鎖一起使用。使用時,條件變數被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖並等待條件發生變化。一旦其它的某個線程改變了條件變數,它將通知相應的條件變數喚醒一個或多個正被此條件變數阻塞的線程。這些線程將重新鎖定互斥鎖並重新測試條件是否滿足。

四、線程管理相關代碼
複製代碼 代碼如下://省略了線程互斥量以及條件變數的初始化
//線程管理:阻塞sec秒讀取線程資訊
//三個參數分別為:線程資訊、線程ID、逾時秒數
bool ManagePthread_TimeReadSignal(PTHREAD_BUF *rbuf, PTHREAD_ID thread_num, int sec)
{
bool b_valid = false;
struct timespec to;
int err;
to.tv_sec = time(NULL) + sec;
to.tv_nsec = 0;

//上鎖
pthread_mutex_lock(&managePthread.g_pthread_mutex[thread_num]);
//逾時sec秒阻塞等待,類似select
err = pthread_cond_timedwait(&managePthread.g_pthread_cond[thread_num], &managePthread.g_pthread_mutex[thread_num], &to);
if(err == ETIMEDOUT)
{
pthread_mutex_unlock(&managePthread.g_pthread_mutex[thread_num]);
return false;
}

//擷取線程資訊
if(managePthread.g_pthread_info[thread_num] == WRITE_FLAG)
{
managePthread.g_pthread_info[thread_num] = READ_FLAG;
memcpy((PTHREAD_BUF *)rbuf, (PTHREAD_BUF *)&managePthread.g_pthread_buf[thread_num], sizeof(PTHREAD_BUF));
b_valid = true;
}

//解鎖
pthread_mutex_unlock(&managePthread.g_pthread_mutex[thread_num]);
return b_valid;
}
//阻塞讀取線程資訊
bool ManagePthread_ReadSignal(PTHREAD_BUF *rbuf, PTHREAD_ID thread_num, bool wait)
{
bool b_valid = false;
pthread_mutex_lock(&managePthread.g_pthread_mutex[thread_num]);
if(wait == true)
pthread_cond_wait(&managePthread.g_pthread_cond[thread_num], &managePthread.g_pthread_mutex[thread_num]);
if(managePthread.g_pthread_info[thread_num] == WRITE_FLAG)
{
managePthread.g_pthread_info[thread_num] = READ_FLAG;
memcpy((PTHREAD_BUF *)rbuf, (PTHREAD_BUF *)&managePthread.g_pthread_buf[thread_num], sizeof(PTHREAD_BUF));
b_valid = true;
}
pthread_mutex_unlock(&managePthread.g_pthread_mutex[thread_num]);
return b_valid;
}
//啟用/發送線程資訊
bool ManagePthread_SendSignal(PTHREAD_BUF *sbuf, PTHREAD_ID thread_num)
{
bool b_valid = false;
pthread_mutex_lock(&managePthread.g_pthread_mutex[thread_num]);
managePthread.g_pthread_info[thread_num] = WRITE_FLAG;
if(sbuf)
{
memcpy((PTHREAD_BUF *)&managePthread.g_pthread_buf[thread_num], (PTHREAD_BUF *)sbuf, sizeof(PTHREAD_BUF));
}
pthread_mutex_unlock(&managePthread.g_pthread_mutex[thread_num]);
pthread_cond_signal(&managePthread.g_pthread_cond[thread_num]);
b_valid = true;
return b_valid;
}
//廣播
bool ManagePthread_BroadcastSignal(PTHREAD_BUF *sbuf, PTHREAD_ID thread_num)
{
bool b_valid = false;
pthread_mutex_lock(&managePthread.g_pthread_mutex[thread_num]);
managePthread.g_pthread_info[thread_num] = WRITE_FLAG;
memcpy((PTHREAD_BUF *)&managePthread.g_pthread_buf[thread_num], (PTHREAD_BUF *)sbuf, sizeof(PTHREAD_BUF));
pthread_mutex_unlock(&managePthread.g_pthread_mutex[thread_num]);
pthread_cond_broadcast(&managePthread.g_pthread_cond[thread_num]);
b_valid = true;
return b_valid;
}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.