|
條件變量函數(shù)
|
操作 |
相關(guān)函數(shù)說明 |
|
初始化條件變量 |
pthread_cond_init 語法 |
|
基于條件變量阻塞 |
pthread_cond_wait 語法 |
|
解除阻塞特定線程 |
pthread_cond_signal 語法 |
|
在指定的時(shí)間之前阻塞 |
pthread_cond_timedwait 語法 |
|
在指定的時(shí)間間隔內(nèi)阻塞 |
pthread_cond_reltimedwait_np 語法 |
|
解除阻塞所有線程 |
pthread_cond_broadcast 語法 |
|
銷毀條件變量狀態(tài) |
pthread_cond_destroy 語法 |
初始化條件變量
使用 pthread_cond_init(3C) 可以將 cv 所指示的條件變量初始化為其缺省值,或者指定已經(jīng)使用 pthread_condattr_init() 設(shè)置的條件變量屬性。
pthread_cond_init 語法
int pthread_cond_init(pthread_cond_t *cv,
const pthread_condattr_t *cattr); #include <pthread.h>
pthread_cond_t cv;
pthread_condattr_t cattr;
int ret;
/* initialize a condition variable to its default value */
ret = pthread_cond_init(&cv, NULL);
/* initialize a condition variable */
ret = pthread_cond_init(&cv, &cattr);
cattr 設(shè)置為 NULL。將 cattr 設(shè)置為 NULL 與傳遞缺省條件變量屬性對象的地址等效,但是沒有內(nèi)存開銷。對于 Solaris 線程,請參見cond_init 語法。
使用 PTHREAD_COND_INITIALIZER 宏可以將以靜態(tài)方式定義的條件變量初始化為其缺省屬性。PTHREAD_COND_INITIALIZER 宏與動(dòng)態(tài)分配具有 null 屬性的 pthread_cond_init() 等效,但是不進(jìn)行錯(cuò)誤檢查。
多個(gè)線程決不能同時(shí)初始化或重新初始化同一個(gè)條件變量。如果要重新初始化或銷毀某個(gè)條件變量,則應(yīng)用程序必須確保該條件變量未被使用。
pthread_cond_init 返回值
pthread_cond_init() 在成功完成之后會(huì)返回零。其他任何返回值都表示出現(xiàn)了錯(cuò)誤。如果出現(xiàn)以下任一情況,該函數(shù)將失敗并返回對應(yīng)的值。
EINVAL
描述:
cattr 指定的值無效。
EBUSY
描述:
條件變量處于使用狀態(tài)。
EAGAIN
描述:
必要的資源不可用。
ENOMEM
描述:
內(nèi)存不足,無法初始化條件變量。
基于條件變量阻塞
使用 pthread_cond_wait(3C) 可以以原子方式釋放 mp 所指向的互斥鎖,并導(dǎo)致調(diào)用線程基于 cv 所指向的條件變量阻塞。對于 Solaris 線程,請參見cond_wait 語法。
pthread_cond_wait 語法
int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex); #include <pthread.h>
pthread_cond_t cv;
pthread_mutex_t mp;
int ret;
/* wait on condition variable */
ret = pthread_cond_wait(&cv, &mp);
阻塞的線程可以通過 pthread_cond_signal() 或 pthread_cond_broadcast() 喚醒,也可以在信號傳送將其中斷時(shí)喚醒。
不能通過 pthread_cond_wait() 的返回值來推斷與條件變量相關(guān)聯(lián)的條件的值的任何變化。必須重新評估此類條件。
pthread_cond_wait() 例程每次返回結(jié)果時(shí)調(diào)用線程都會(huì)鎖定并且擁有互斥鎖,即使返回錯(cuò)誤時(shí)也是如此。
該條件獲得信號之前,該函數(shù)一直被阻塞。該函數(shù)會(huì)在被阻塞之前以原子方式釋放相關(guān)的互斥鎖,并在返回之前以原子方式再次獲取該互斥鎖。
通常,對條件表達(dá)式的評估是在互斥鎖的保護(hù)下進(jìn)行的。如果條件表達(dá)式為假,線程會(huì)基于條件變量阻塞。然后,當(dāng)該線程更改條件值時(shí),另一個(gè)線程會(huì)針對條件變量發(fā)出信號。這種變化會(huì)導(dǎo)致所有等待該條件的線程解除阻塞并嘗試再次獲取互斥鎖。
必須重新測試導(dǎo)致等待的條件,然后才能從 pthread_cond_wait() 處繼續(xù)執(zhí)行。喚醒的線程重新獲取互斥鎖并從 pthread_cond_wait() 返回之前,條件可能會(huì)發(fā)生變化。等待線程可能并未真正喚醒。建議使用的測試方法是,將條件檢查編寫為調(diào)用 pthread_cond_wait() 的 while() 循環(huán)。 pthread_mutex_lock();
while(condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
如果有多個(gè)線程基于該條件變量阻塞,則無法保證按特定的順序獲取互斥鎖。
注 –
pthread_cond_wait() 是取消點(diǎn)。如果取消處于暫掛狀態(tài),并且調(diào)用線程啟用了取消功能,則該線程會(huì)終止,并在繼續(xù)持有該鎖的情況下開始執(zhí)行清除處理程序。
pthread_cond_wait 返回值
pthread_cond_wait() 在成功完成之后會(huì)返回零。其他任何返回值都表示出現(xiàn)了錯(cuò)誤。如果出現(xiàn)以下情況,該函數(shù)將失敗并返回對應(yīng)的值。
EINVAL
描述:
cv 或 mp 指定的值無效。
解除阻塞一個(gè)線程
對于基于 cv 所指向的條件變量阻塞的線程,使用 pthread_cond_signal(3C) 可以解除阻塞該線程。對于 Solaris 線程,請參見cond_signal 語法。
pthread_cond_signal 語法
int pthread_cond_signal(pthread_cond_t *cv); #include <pthread.h>
pthread_cond_t cv;
int ret;
/* one condition variable is signaled */
ret = pthread_cond_signal(&cv);
應(yīng)在互斥鎖的保護(hù)下修改相關(guān)條件,該互斥鎖用于獲得信號的條件變量中。否則,可能在條件變量的測試和 pthread_cond_wait() 阻塞之間修改該變量,這會(huì)導(dǎo)致無限期等待。
調(diào)度策略可確定喚醒阻塞線程的順序。對于 SCHED_OTHER,將按優(yōu)先級順序喚醒線程。
如果沒有任何線程基于條件變量阻塞,則調(diào)用 pthread_cond_signal() 不起作用。
示例 4–8 使用 pthread_cond_wait() 和 pthread_cond_signal()
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count()
{
pthread_mutex_lock(&count_lock);
while (count == 0)
pthread_cond_wait(&count_nonzero, &count_lock);
count = count - 1;
pthread_mutex_unlock(&count_lock);
}
increment_count()
{
pthread_mutex_lock(&count_lock);
if (count == 0)
pthread_cond_signal(&count_nonzero);
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
pthread_cond_signal 返回值
pthread_cond_signal() 在成功完成之后會(huì)返回零。其他任何返回值都表示出現(xiàn)了錯(cuò)誤。如果出現(xiàn)以下情況,該函數(shù)將失敗并返回對應(yīng)的值。
EINVAL
描述:
cv 指向的地址非法。
說明了如何使用 pthread_cond_wait() 和 pthread_cond_signal()。
在指定的時(shí)間之前阻塞
pthread_cond_timedwait(3C) 的用法與 pthread_cond_wait() 的用法基本相同,區(qū)別在于在由 abstime 指定的時(shí)間之后 pthread_cond_timedwait() 不再被阻塞。
pthread_cond_timedwait 語法
int pthread_cond_timedwait(pthread_cond_t *cv,
pthread_mutex_t *mp, const struct timespec *abstime); #include <pthread.h>
#include <time.h>
pthread_cond_t cv;
pthread_mutex_t mp;
timestruct_t abstime;
int ret;
/* wait on condition variable */
ret = pthread_cond_timedwait(&cv, &mp, &abstime);
pthread_cond_timewait() 每次返回時(shí)調(diào)用線程都會(huì)鎖定并且擁有互斥鎖,即使 pthread_cond_timedwait() 返回錯(cuò)誤時(shí)也是如此。 對于 Solaris 線程
pthread_cond_timedwait() 函數(shù)會(huì)一直阻塞,直到該條件獲得信號,或者最后一個(gè)參數(shù)所指定的時(shí)間已過為止。
注 –
pthread_cond_timedwait() 也是取消點(diǎn)。
示例 4–9 計(jì)時(shí)條件等待
pthread_timestruc_t to;
pthread_mutex_t m;
pthread_cond_t c;
...
pthread_mutex_lock(&m);
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
while (cond == FALSE) {
err = pthread_cond_timedwait(&c, &m, &to);
if (err == ETIMEDOUT) {
/* timeout, do something */
break;
}
}
pthread_mutex_unlock(&m);
pthread_cond_timedwait 返回值
pthread_cond_timedwait() 在成功完成之后會(huì)返回零。其他任何返回值都表示出現(xiàn)了錯(cuò)誤。如果出現(xiàn)以下任一情況,該函數(shù)將失敗并返回對應(yīng)的值。
EINVAL
描述:
cv 或 abstime 指向的地址非法。
ETIMEDOUT
描述:
abstime 指定的時(shí)間已過。
超時(shí)會(huì)指定為當(dāng)天時(shí)間,以便在不重新計(jì)算值的情況下高效地重新測試條件,如示例 4–9 中所示。
在指定的時(shí)間間隔內(nèi)阻塞
pthread_cond_reltimedwait_np(3C) 的用法與 pthread_cond_timedwait() 的用法基本相同,唯一的區(qū)別在于 pthread_cond_reltimedwait_np() 會(huì)采用相對時(shí)間間隔而不是將來的絕對時(shí)間作為其最后一個(gè)參數(shù)的值。
pthread_cond_reltimedwait_np 語法
int pthread_cond_reltimedwait_np(pthread_cond_t *cv,
pthread_mutex_t *mp,
const struct timespec *reltime); #include <pthread.h>
#include <time.h>
pthread_cond_t cv;
pthread_mutex_t mp;
timestruct_t reltime;
int ret;
/* wait on condition variable */
ret = pthread_cond_reltimedwait_np(&cv, &mp, &reltime);
pthread_cond_reltimedwait_np() 每次返回時(shí)調(diào)用線程都會(huì)鎖定并且擁有互斥鎖,即使 pthread_cond_reltimedwait_np() 返回錯(cuò)誤時(shí)也是如此。對于 Solaris 線程,請參見 cond_reltimedwait(3C)。pthread_cond_reltimedwait_np() 函數(shù)會(huì)一直阻塞,直到該條件獲得信號,或者最后一個(gè)參數(shù)指定的時(shí)間間隔已過為止。
注 –
pthread_cond_reltimedwait_np() 也是取消點(diǎn)。
pthread_cond_reltimedwait_np 返回值
pthread_cond_reltimedwait_np() 在成功完成之后會(huì)返回零。其他任何返回值都表示出現(xiàn)了錯(cuò)誤。如果出現(xiàn)以下任一情況,該函數(shù)將失敗并返回對應(yīng)的值。
EINVAL
描述:
cv 或 reltime 指示的地址非法。
ETIMEDOUT
描述:
reltime 指定的時(shí)間間隔已過。
解除阻塞所有線程
對于基于 cv 所指向的條件變量阻塞的線程,使用 pthread_cond_broadcast(3C) 可以解除阻塞所有這些線程,這由 pthread_cond_wait() 來指定。
pthread_cond_broadcast 語法
int pthread_cond_broadcast(pthread_cond_t *cv); #include <pthread.h>
pthread_cond_t cv;
int ret;
/* all condition variables are signaled */
ret = pthread_cond_broadcast(&cv);
如果沒有任何線程基于該條件變量阻塞,則調(diào)用 pthread_cond_broadcast() 不起作用。對于 Solaris 線程,請參見cond_broadcast 語法。
由于 pthread_cond_broadcast() 會(huì)導(dǎo)致所有基于該條件阻塞的線程再次爭用互斥鎖,因此請謹(jǐn)慎使用 pthread_cond_broadcast()。例如,通過使用 pthread_cond_broadcast(),線程可在資源釋放后爭用不同的資源量,如示例 4–10 中所示。
示例 4–10 條件變量廣播
pthread_mutex_t rsrc_lock;
pthread_cond_t rsrc_add;
unsigned int resources;
get_resources(int amount)
{
pthread_mutex_lock(&rsrc_lock);
while (resources < amount) {
pthread_cond_wait(&rsrc_add, &rsrc_lock);
}
resources -= amount;
pthread_mutex_unlock(&rsrc_lock);
}
add_resources(int amount)
{
pthread_mutex_lock(&rsrc_lock);
resources += amount;
pthread_cond_broadcast(&rsrc_add);
pthread_mutex_unlock(&rsrc_lock);
}
請注意,在 add_resources() 中,首先更新 resources 還是首先在互斥鎖中調(diào)用 pthread_cond_broadcast() 無關(guān)緊要。
應(yīng)在互斥鎖的保護(hù)下修改相關(guān)條件,該互斥鎖用于獲得信號的條件變量中。否則,可能在條件變量的測試和 pthread_cond_wait() 阻塞之間修改該變量,這會(huì)導(dǎo)致無限期等待。
pthread_cond_broadcast 返回值
pthread_cond_broadcast() 在成功完成之后會(huì)返回零。其他任何返回值都表示出現(xiàn)了錯(cuò)誤。如果出現(xiàn)以下情況,該函數(shù)將失敗并返回對應(yīng)的值。
EINVAL
描述:
cv 指示的地址非法。
銷毀條件變量狀態(tài)
使用 pthread_cond_destroy(3C) 可以銷毀與 cv 所指向的條件變量相關(guān)聯(lián)的任何狀態(tài)。對于 Solaris 線程,請參見cond_destroy 語法。
pthread_cond_destroy 語法
int pthread_cond_destroy(pthread_cond_t *cv); #include <pthread.h>
pthread_cond_t cv;
int ret;
/* Condition variable is destroyed */
ret = pthread_cond_destroy(&cv);
請注意,沒有釋放用來存儲(chǔ)條件變量的空間。
pthread_cond_destroy 返回值
pthread_cond_destroy() 在成功完成之后會(huì)返回零。其他任何返回值都表示出現(xiàn)了錯(cuò)誤。如果出現(xiàn)以下情況,該函數(shù)將失敗并返回對應(yīng)的值。
EINVAL
描述:
cv 指定的值無效。
注意:pthread_cond_destroy 銷毀一個(gè)條件變量,釋放它擁有的資源。進(jìn)入 pthread_cond_destroy 之前,必須沒有在該條件變量上等待的線程。 Attempting to destroy a condition variable upon which other threads are currently blocked results in undefined behavior.
喚醒丟失問題
如果線程未持有與條件相關(guān)聯(lián)的互斥鎖,則調(diào)用 pthread_cond_signal() 或 pthread_cond_broadcast() 會(huì)產(chǎn)生喚醒丟失錯(cuò)誤。
滿足以下所有條件時(shí),即會(huì)出現(xiàn)喚醒丟失問題:
僅當(dāng)修改所測試的條件但未持有與之相關(guān)聯(lián)的互斥鎖時(shí),才會(huì)出現(xiàn)此問題。只要僅在持有關(guān)聯(lián)的互斥鎖同時(shí)修改所測試的條件,即可調(diào)用 pthread_cond_signal() 和 pthread_cond_broadcast(),而無論這些函數(shù)是否持有關(guān)聯(lián)的互斥鎖。
生成方和使用者問題
并發(fā)編程中收集了許多標(biāo)準(zhǔn)的眾所周知的問題,生成方和使用者問題只是其中的一個(gè)問題。此問題涉及到一個(gè)大小限定的緩沖區(qū)和兩類線程(生成方和使用者),生成方將項(xiàng)放入緩沖區(qū)中,然后使用者從緩沖區(qū)中取走項(xiàng)。
生成方必須在緩沖區(qū)中有可用空間之后才能向其中放置內(nèi)容。使用者必須在生成方向緩沖區(qū)中寫入之后才能從中提取內(nèi)容。
條件變量表示一個(gè)等待某個(gè)條件獲得信號的線程隊(duì)列。
示例 4–11 中包含兩個(gè)此類隊(duì)列。一個(gè)隊(duì)列 (less) 針對生成方,用于等待緩沖區(qū)中出現(xiàn)空位置。另一個(gè)隊(duì)列 (more) 針對使用者,用于等待從緩沖槽位的空位置中提取其中包含的信息。該示例中還包含一個(gè)互斥鎖,因?yàn)槊枋鲈摼彌_區(qū)的數(shù)據(jù)結(jié)構(gòu)一次只能由一個(gè)線程訪問。
示例 4–11 生成方和使用者的條件變量問題
typedef struct {
char buf[BSIZE];
int occupied;
int nextin;
int nextout;
pthread_mutex_t mutex;
pthread_cond_t more;
pthread_cond_t less;
} buffer_t;
buffer_t buffer;
如示例 4–12 中所示,生成方線程獲取該互斥鎖以保護(hù) buffer 數(shù)據(jù)結(jié)構(gòu),然后,緩沖區(qū)確定是否有空間可用于存放所生成的項(xiàng)。如果沒有可用空間,生成方線程會(huì)調(diào)用 pthread_cond_wait()。pthread_cond_wait() 會(huì)導(dǎo)致生成方線程連接正在等待 less 條件獲得信號的線程隊(duì)列。less 表示緩沖區(qū)中的可用空間。
與此同時(shí),在調(diào)用 pthread_cond_wait() 的過程中,該線程會(huì)釋放互斥鎖的鎖定。正在等待的生成方線程依賴于使用者線程在條件為真時(shí)發(fā)出信號,如示例 4–12 中所示。該條件獲得信號時(shí),將會(huì)喚醒等待 less 的第一個(gè)線程。但是,該線程必須再次鎖定互斥鎖,然后才能從 pthread_cond_wait() 返回。
獲取互斥鎖可確保該線程再次以獨(dú)占方式訪問緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu)。該線程隨后必須檢查緩沖區(qū)中是否確實(shí)存在可用空間。如果空間可用,該線程會(huì)向下一個(gè)可用的空位置中進(jìn)行寫入。
與此同時(shí),使用者線程可能正在等待項(xiàng)出現(xiàn)在緩沖區(qū)中。這些線程正在等待條件變量 more。剛在緩沖區(qū)中存儲(chǔ)內(nèi)容的生成方線程會(huì)調(diào)用 pthread_cond_signal() 以喚醒下一個(gè)正在等待的使用者。如果沒有正在等待的使用者,此調(diào)用將不起作用。
最后,生成方線程會(huì)解除鎖定互斥鎖,從而允許其他線程處理緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu)。
示例 4–12 生成方和使用者問題:生成方
void producer(buffer_t *b, char item)
{
pthread_mutex_lock(&b->mutex);
while (b->occupied >= BSIZE)
pthread_cond_wait(&b->less, &b->mutex);
assert(b->occupied < BSIZE);
b->buf[b->nextin++] = item;
b->nextin %= BSIZE;
b->occupied++;
/* now: either b->occupied < BSIZE and b->nextin is the index
of the next empty slot in the buffer, or
b->occupied == BSIZE and b->nextin is the index of the
next (occupied) slot that will be emptied by a consumer
(such as b->nextin == b->nextout) */
pthread_cond_signal(&b->more);
pthread_mutex_unlock(&b->mutex);
}
請注意 assert() 語句的用法。除非在編譯代碼時(shí)定義了 NDEBUG,否則 assert() 在其參數(shù)的計(jì)算結(jié)果為真(非零)時(shí)將不執(zhí)行任何操作。如果參數(shù)的計(jì)算結(jié)果為假(零),則該程序會(huì)中止。在多線程程序中,此類斷言特別有用。如果斷言失敗,assert() 會(huì)立即指出運(yùn)行時(shí)問題。assert() 還有另一個(gè)作用,即提供有用的注釋。
以 /* now: either b->occupied ... 開頭的注釋最好以斷言形式表示,但是由于語句過于復(fù)雜,無法用布爾值表達(dá)式來表示,因此將用英語表示。
斷言和注釋都是不變量的示例。這些不變量是邏輯語句,在程序正常執(zhí)行時(shí)不應(yīng)將其聲明為假,除非是線程正在修改不變量中提到的一些程序變量時(shí)的短暫修改過程中。當(dāng)然,只要有線程執(zhí)行語句,斷言就應(yīng)當(dāng)為真。
使用不變量是一種極為有用的方法。即使沒有在程序文本中聲明不變量,在分析程序時(shí)也應(yīng)將其視為不變量。
每次線程執(zhí)行包含注釋的代碼時(shí),生成方代碼中表示為注釋的不變量始終為真。如果將此注釋移到緊挨 mutex_unlock() 的后面,則注釋不一定仍然為真。如果將此注釋移到緊跟 assert() 之后的位置,則注釋仍然為真。
因此,不變量可用于表示一個(gè)始終為真的屬性,除非一個(gè)生成方或一個(gè)使用者正在更改緩沖區(qū)的狀態(tài)。線程在互斥鎖的保護(hù)下處理緩沖區(qū)時(shí),該線程可能會(huì)暫時(shí)聲明不變量為假。但是,一旦線程結(jié)束對緩沖區(qū)的操作,不變量即會(huì)恢復(fù)為真。
示例 4–13 給出了使用者的代碼。該邏輯流程與生成方的邏輯流程相對稱。
示例 4–13 生成方和使用者問題:使用者
char consumer(buffer_t *b)
{
char item;
pthread_mutex_lock(&b->mutex);
while(b->occupied <= 0)
pthread_cond_wait(&b->more, &b->mutex);
assert(b->occupied > 0);
item = b->buf[b->nextout++];
b->nextout %= BSIZE;
b->occupied--;
/* now: either b->occupied > 0 and b->nextout is the index
of the next occupied slot in the buffer, or
b->occupied == 0 and b->nextout is the index of the next
(empty) slot that will be filled by a producer (such as
b->nextout == b->nextin) */
pthread_cond_signal(&b->less);
pthread_mutex_unlock(&b->mutex);
return(item);
}
|