[原文地址]http://www.ibm.com/developerworks/cn/linux/thread/posix_thread3/
本文是 POSIX 線程三部曲系列的最后一部分,Daniel 將詳細(xì)討論如何使用條件變量。條件變量是 POSIX 線程結(jié)構(gòu),可以讓您在遇到某些條件時(shí)“喚醒”線程。可以將它們看作是一種線程安全的信號(hào)發(fā)送。Daniel 使用目前您所學(xué)到的知識(shí)實(shí)現(xiàn)了一個(gè)多線程工作組應(yīng)用程序,本文將圍繞著這一示例而進(jìn)行討論。
條件變量詳解
在 上一篇文章結(jié)束時(shí),我描述了一個(gè)比較特殊的難題:如果線程正在等待某個(gè)特定條件發(fā)生,它應(yīng)該如何處理這種情況?它可以重復(fù)對(duì)互斥對(duì)象鎖定和解鎖,每次都會(huì)檢查共享數(shù)據(jù)結(jié)構(gòu),以查找某個(gè)值。但這是在浪費(fèi)時(shí)間和資源,而且這種繁忙查詢的效率非常低。解決這個(gè)問題的最佳方法是使用 pthread_cond_wait() 調(diào)用來等待特殊條件發(fā)生。
了解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 線程信號(hào)發(fā)送系統(tǒng)的核心,也是最難以理解的部分。
首先,讓我們考慮以下情況:線程為查看已鏈接列表而鎖定了互斥對(duì)象,然而該列表恰巧是空的。這一特定線程什么也干不了 -- 其設(shè)計(jì)意圖是從列表中除去節(jié)點(diǎn),但是現(xiàn)在卻沒有節(jié)點(diǎn)。因此,它只能:
鎖定互斥對(duì)象時(shí),線程將調(diào)用 pthread_cond_wait(&mycond,&mymutex)。pthread_cond_wait() 調(diào)用相當(dāng)復(fù)雜,因此我們每次只執(zhí)行它的一個(gè)操作。
pthread_cond_wait() 所做的第一件事就是同時(shí)對(duì)互斥對(duì)象解鎖(于是其它線程可以修改已鏈接列表),并等待條件 mycond 發(fā)生(這樣當(dāng) pthread_cond_wait() 接收到另一個(gè)線程的“信號(hào)”時(shí),它將蘇醒)。現(xiàn)在互斥對(duì)象已被解鎖,其它線程可以訪問和修改已鏈接列表,可能還會(huì)添加項(xiàng)。
此時(shí),pthread_cond_wait() 調(diào)用還未返回。對(duì)互斥對(duì)象解鎖會(huì)立即發(fā)生,但等待條件 mycond 通常是一個(gè)阻塞操作,這意味著線程將睡眠,在它蘇醒之前不會(huì)消耗 CPU 周期。這正是我們期待發(fā)生的情況。線程將一直睡眠,直到特定條件發(fā)生,在這期間不會(huì)發(fā)生任何浪費(fèi) CPU 時(shí)間的繁忙查詢。從線程的角度來看,它只是在等待 pthread_cond_wait() 調(diào)用返回。
現(xiàn)在繼續(xù)說明,假設(shè)另一個(gè)線程(稱作“2 號(hào)線程”)鎖定了 mymutex 并對(duì)已鏈接列表添加了一項(xiàng)。在對(duì)互斥對(duì)象解鎖之后,2 號(hào)線程會(huì)立即調(diào)用函數(shù) pthread_cond_broadcast(&mycond)。此操作之后,2 號(hào)線程將使所有等待 mycond 條件變量的線程立即蘇醒。這意味著第一個(gè)線程(仍處于 pthread_cond_wait() 調(diào)用中)現(xiàn)在將蘇醒。
現(xiàn)在,看一下第一個(gè)線程發(fā)生了什么。您可能會(huì)認(rèn)為在 2 號(hào)線程調(diào)用 pthread_cond_broadcast(&mymutex) 之后,1 號(hào)線程的 pthread_cond_wait() 會(huì)立即返回。不是那樣!實(shí)際上,pthread_cond_wait() 將執(zhí)行最后一個(gè)操作:重新鎖定 mymutex。一旦 pthread_cond_wait() 鎖定了互斥對(duì)象,那么它將返回并允許 1 號(hào)線程繼續(xù)執(zhí)行。那時(shí),它可以馬上檢查列表,查看它所感興趣的更改。
停止并回顧!
那個(gè)過程非常復(fù)雜,因此讓我們先來回顧一下。第一個(gè)線程首先調(diào)用:
pthread_mutex_lock(&mymutex);
|
然后,它檢查了列表。沒有找到感興趣的東西,于是它調(diào)用:
pthread_cond_wait(&mycond, &mymutex);
|
然后,pthread_cond_wait() 調(diào)用在返回前執(zhí)行許多操作:
pthread_mutex_unlock(&mymutex);
|
它對(duì) mymutex 解鎖,然后進(jìn)入睡眠狀態(tài),等待 mycond 以接收 POSIX 線程“信號(hào)”。一旦接收到“信號(hào)”(加引號(hào)是因?yàn)槲覀儾⒉皇窃谟懻搨鹘y(tǒng)的 UNIX 信號(hào),而是來自 pthread_cond_signal() 或 pthread_cond_broadcast() 調(diào)用的信號(hào)),它就會(huì)蘇醒。但 pthread_cond_wait() 沒有立即返回 -- 它還要做一件事:重新鎖定 mutex:
pthread_mutex_lock(&mymutex);
|
pthread_cond_wait() 知道我們?cè)诓檎?mymutex “背后”的變化,因此它繼續(xù)操作,為我們鎖定互斥對(duì)象,然后才返回。
pthread_cond_wait() 小測(cè)驗(yàn)
現(xiàn)在已回顧了 pthread_cond_wait() 調(diào)用,您應(yīng)該了解了它的工作方式。應(yīng)該能夠敘述 pthread_cond_wait() 依次執(zhí)行的所有操作。嘗試一下。如果理解了 pthread_cond_wait(),其余部分就相當(dāng)容易,因此請(qǐng)重新閱讀以上部分,直到記住為止。好,讀完之后,能否告訴我在調(diào)用 pthread_cond_wait() 之 前,互斥對(duì)象必須處于什么狀態(tài)?pthread_cond_wait() 調(diào)用返回之后,互斥對(duì)象處于什么狀態(tài)?這兩個(gè)問題的答案都是“鎖定”。既然已經(jīng)完全理解了 pthread_cond_wait() 調(diào)用,現(xiàn)在來繼續(xù)研究更簡(jiǎn)單的東西 -- 初始化和真正的發(fā)送信號(hào)和廣播進(jìn)程。到那時(shí),我們將會(huì)對(duì)包含了多線程工作隊(duì)列的 C 代碼了如指掌。
初始化和清除
條件變量是一個(gè)需要初始化的真實(shí)數(shù)據(jù)結(jié)構(gòu)。以下就初始化的方法。首先,定義或分配一個(gè)條件變量,如下所示:
然后,調(diào)用以下函數(shù)進(jìn)行初始化:
pthread_cond_init(&mycond,NULL);
|
瞧,初始化完成了!在釋放或廢棄條件變量之前,需要?dú)乃缦滤荆?/p>
pthread_cond_destroy(&mycond);
|
很簡(jiǎn)單吧。接著討論 pthread_cond_wait() 調(diào)用。
等待
一旦初始化了互斥對(duì)象和條件變量,就可以等待某個(gè)條件,如下所示:
pthread_cond_wait(&mycond, &mymutex);
|
請(qǐng)注意,代碼在邏輯上應(yīng)該包含 mycond 和 mymutex。一個(gè)特定條件只能有一個(gè)互斥對(duì)象,而且條件變量應(yīng)該表示互斥數(shù)據(jù)“內(nèi)部”的一種特殊的條件更改。一個(gè)互斥對(duì)象可以用許多條件變量(例如,cond_empty、cond_full、cond_cleanup),但每個(gè)條件變量只能有一個(gè)互斥對(duì)象。
發(fā)送信號(hào)和廣播
對(duì)于發(fā)送信號(hào)和廣播,需要注意一點(diǎn)。如果線程更改某些共享數(shù)據(jù),而且它想要喚醒所有正在等待的線程,則應(yīng)使用 pthread_cond_broadcast 調(diào)用,如下所示:
pthread_cond_broadcast(&mycond);
|
在某些情況下,活動(dòng)線程只需要喚醒第一個(gè)正在睡眠的線程。假設(shè)您只對(duì)隊(duì)列添加了一個(gè)工作作業(yè)。那么只需要喚醒一個(gè)工作程序線程(再喚醒其它線程是不禮貌的!):
pthread_cond_signal(&mycond);
|
此函數(shù)只喚醒一個(gè)線程。如果 POSIX 線程標(biāo)準(zhǔn)允許指定一個(gè)整數(shù),可以讓您喚醒一定數(shù)量的正在睡眠的線程,那就更完美了。但是很可惜,我沒有被邀請(qǐng)參加會(huì)議。
工作組
我將演示如何創(chuàng)建多線程工作組。在這個(gè)方案中,我們創(chuàng)建了許多工作程序線程。每個(gè)線程都會(huì)檢查 wq(“工作隊(duì)列”),查看是否有需要完成的工作。如果有需要完成的工作,那么線程將從隊(duì)列中除去一個(gè)節(jié)點(diǎn),執(zhí)行這些特定工作,然后等待新的工作到達(dá)。
與此同時(shí),主線程負(fù)責(zé)創(chuàng)建這些工作程序線程、將工作添加到隊(duì)列,然后在它退出時(shí)收集所有工作程序線程。您將會(huì)遇到許多 C 代碼,好好準(zhǔn)備吧!
隊(duì)列
需要隊(duì)列是出于兩個(gè)原因。首先,需要隊(duì)列來保存工作作業(yè)。還需要可用于跟蹤已終止線程的數(shù)據(jù)結(jié)構(gòu)。還記得前幾篇文章(請(qǐng)參閱本文結(jié)尾處的 參考資料)中,我曾提到過需要使用帶有特定進(jìn)程標(biāo)識(shí)的 pthread_join 嗎?使用“清除隊(duì)列”(稱作 "cq")可以解決無法等待 任何已終止線程的問題(稍后將詳細(xì)討論這個(gè)問題)。以下是標(biāo)準(zhǔn)隊(duì)列代碼。將此代碼保存到文件 queue.h 和 queue.c:
queue.h
/* queue.h
** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
** Author: Daniel Robbins
** Date: 16 Jun 2000
*/
typedef struct node {
struct node *next;
} node;
typedef struct queue {
node *head, *tail;
} queue;
void queue_init(queue *myroot);
void queue_put(queue *myroot, node *mynode);
node *queue_get(queue *myroot);
|
queue.c
/* queue.c
** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
** Author: Daniel Robbins
** Date: 16 Jun 2000
**
** This set of queue functions was originally thread-aware. I
** redesigned the code to make this set of queue routines
** thread-ignorant (just a generic, boring yet very fast set of queue
** routines). Why the change? Because it makes more sense to have
** the thread support as an optional add-on. Consider a situation
** where you want to add 5 nodes to the queue. With the
** thread-enabled version, each call to queue_put() would
** automatically lock and unlock the queue mutex 5 times -- that's a
** lot of unnecessary overhead. However, by moving the thread stuff
** out of the queue routines, the caller can lock the mutex once at
** the beginning, then insert 5 items, and then unlock at the end.
** Moving the lock/unlock code out of the queue functions allows for
** optimizations that aren't possible otherwise. It also makes this
** code useful for non-threaded applications.
**
** We can easily thread-enable this data structure by using the
** data_control type defined in control.c and control.h. */
#include <stdio.h>
#include "queue.h"
void queue_init(queue *myroot) {
myroot->head=NULL;
myroot->tail=NULL;
}
void queue_put(queue *myroot,node *mynode) {
mynode->next=NULL;
if (myroot->tail!=NULL)
myroot->tail->next=mynode;
myroot->tail=mynode;
if (myroot->:head==NULL)
myroot->head=mynode;
}
node *queue_get(queue *myroot) {
//get from root
node *mynode;
mynode=myroot->head;
if (myroot->head!=NULL)
myroot->head=myroot->head->next;
return mynode;
}
|
data_control 代碼
我編寫的并不是線程安全的隊(duì)列例程,事實(shí)上我創(chuàng)建了一個(gè)“數(shù)據(jù)包裝”或“控制”結(jié)構(gòu),它可以是任何線程支持的數(shù)據(jù)結(jié)構(gòu)。看一下 control.h:
control.h
#include
typedef struct data_control {
pthread_mutex_t mutex;
pthread_cond_t cond;
int active;
} data_control;
|
現(xiàn)在您看到了 data_control 結(jié)構(gòu)定義,以下是它的視覺表示:
所使用的 data_control 結(jié)構(gòu)
圖像中的鎖代表互斥對(duì)象,它允許對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行互斥訪問。黃色的星代表?xiàng)l件變量,它可以睡眠,直到所討論的數(shù)據(jù)結(jié)構(gòu)改變?yōu)橹埂n/off 開關(guān)表示整數(shù) "active",它告訴線程此數(shù)據(jù)是否是活動(dòng)的。在代碼中,我使用整數(shù) active 作為標(biāo)志,告訴工作隊(duì)列何時(shí)應(yīng)該關(guān)閉。以下是 control.c:
control.c
/* control.c
** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
** Author: Daniel Robbins
** Date: 16 Jun 2000
**
** These routines provide an easy way to make any type of
** data-structure thread-aware. Simply associate a data_control
** structure with the data structure (by creating a new struct, for
** example). Then, simply lock and unlock the mutex, or
** wait/signal/broadcast on the condition variable in the data_control
** structure as needed.
**
** data_control structs contain an int called "active". This int is
** intended to be used for a specific kind of multithreaded design,
** where each thread checks the state of "active" every time it locks
** the mutex. If active is 0, the thread knows that instead of doing
** its normal routine, it should stop itself. If active is 1, it
** should continue as normal. So, by setting active to 0, a
** controlling thread can easily inform a thread work crew to shut
** down instead of processing new jobs. Use the control_activate()
** and control_deactivate() functions, which will also broadcast on
** the data_control struct's condition variable, so that all threads
** stuck in pthread_cond_wait() will wake up, have an opportunity to
** notice the change, and then terminate.
*/
#include "control.h"
int control_init(data_control *mycontrol) {
int mystatus;
if (pthread_mutex_init(&(mycontrol->mutex),NULL))
return 1;
if (pthread_cond_init(&(mycontrol->cond),NULL))
return 1;
mycontrol->active=0;
return 0;
}
int control_destroy(data_control *mycontrol) {
int mystatus;
if (pthread_cond_destroy(&(mycontrol->cond)))
return 1;
if (pthread_cond_destroy(&(mycontrol->cond)))
return 1;
mycontrol->active=0;
return 0;
}
int control_activate(data_control *mycontrol) {
int mystatus;
if (pthread_mutex_lock(&(mycontrol->mutex)))
return 0;
mycontrol->active=1;
pthread_mutex_unlock(&(mycontrol->mutex));
pthread_cond_broadcast(&(mycontrol->cond));
return 1;
}
int control_deactivate(data_control *mycontrol) {
int mystatus;
if (pthread_mutex_lock(&(mycontrol->mutex)))
return 0;
mycontrol->active=0;
pthread_mutex_unlock(&(mycontrol->mutex));
pthread_cond_broadcast(&(mycontrol->cond));
return 1;
}
|
調(diào)試時(shí)間
在開始調(diào)試之前,還需要一個(gè)文件。以下是 dbug.h:
dbug.h
#define dabort() \
{ printf("Aborting at line %d in source file %s\n",__LINE__,__FILE__); abort(); }
|
此代碼用于處理工作組代碼中的不可糾正錯(cuò)誤。
工作組代碼
說到工作組代碼,以下就是:
workcrew.c
#include <stdio.h>
#include <stdlib.h>
#include "control.h"
#include "queue.h"
#include "dbug.h"
/* the work_queue holds tasks for the various threads to complete. */
struct work_queue {
data_control control;
queue work;
} wq;
/* I added a job number to the work node. Normally, the work node
would contain additional data that needed to be processed. */
typedef struct work_node {
struct node *next;
int jobnum;
} wnode;
/* the cleanup queue holds stopped threads. Before a thread
terminates, it adds itself to this list. Since the main thread is
waiting for changes in this list, it will then wake up and clean up
the newly terminated thread. */
struct cleanup_queue {
data_control control;
queue cleanup;
} cq;
/* I added a thread number (for debugging/instructional purposes) and
a thread id to the cleanup node. The cleanup node gets passed to
the new thread on startup, and just before the thread stops, it
attaches the cleanup node to the cleanup queue. The main thread
monitors the cleanup queue and is the one that performs the
necessary cleanup. */
typedef struct cleanup_node {
struct node *next;
int threadnum;
pthread_t tid;
} cnode;
void *threadfunc(void *myarg) {
wnode *mywork;
cnode *mynode;
mynode=(cnode *) myarg;
pthread_mutex_lock(&wq.control.mutex);
while (wq.control.active) {
while (wq.work.head==NULL && wq.control.active) {
pthread_cond_wait(&wq.control.cond, &wq.control.mutex);
}
if (!wq.control.active)
break;
//we got something!
mywork=(wnode *) queue_get(&wq.work);
pthread_mutex_unlock(&wq.control.mutex);
//perform processing...
printf("Thread number %d processing job %d\n",mynode->threadnum,mywork->jobnum);
free(mywork);
pthread_mutex_lock(&wq.control.mutex);
}
pthread_mutex_unlock(&wq.control.mutex);
pthread_mutex_lock(&cq.control.mutex);
queue_put(&cq.cleanup,(node *) mynode);
pthread_mutex_unlock(&cq.control.mutex);
pthread_cond_signal(&cq.control.cond);
printf("thread %d shutting down...\n",mynode->threadnum);
return NULL;
}
#define NUM_WORKERS 4
int numthreads;
void join_threads(void) {
cnode *curnode;
printf("joining threads...\n");
while (numthreads) {
pthread_mutex_lock(&cq.control.mutex);
/* below, we sleep until there really is a new cleanup node. This
takes care of any false wakeups... even if we break out of
pthread_cond_wait(), we don't make any assumptions that the
condition we were waiting for is true. */
while (cq.cleanup.head==NULL) {
pthread_cond_wait(&cq.control.cond,&cq.control.mutex);
}
/* at this point, we hold the mutex and there is an item in the
list that we need to process. First, we remove the node from
the queue. Then, we call pthread_join() on the tid stored in
the node. When pthread_join() returns, we have cleaned up
after a thread. Only then do we free() the node, decrement the
number of additional threads we need to wait for and repeat the
entire process, if necessary */
curnode = (cnode *) queue_get(&cq.cleanup);
pthread_mutex_unlock(&cq.control.mutex);
pthread_join(curnode->tid,NULL);
printf("joined with thread %d\n",curnode->threadnum);
free(curnode);
numthreads--;
}
}
int create_threads(void) {
int x;
cnode *curnode;
for (x=0; x<NUM_WORKERS; x++) {
curnode=malloc(sizeof(cnode));
if (!curnode)
return 1;
curnode->threadnum=x;
if (pthread_create(&curnode->tid, NULL, threadfunc, (void *) curnode))
return 1;
printf("created thread %d\n",x);
numthreads++;
}
return 0;
}
void initialize_structs(void) {
numthreads=0;
if (control_init(&wq.control))
dabort();
queue_init(&wq.work);
if (control_init(&cq.control)) {
control_destroy(&wq.control);
dabort();
}
queue_init(&wq.work);
control_activate(&wq.control);
}
void cleanup_structs(void) {
control_destroy(&cq.control);
control_destroy(&wq.control);
}
int main(void) {
int x;
wnode *mywork;
initialize_structs();
/* CREATION */
if (create_threads()) {
printf("Error starting threads... cleaning up.\n");
join_threads();
dabort();
}
pthread_mutex_lock(&wq.control.mutex);
for (x=0; x<16000; x++) {
mywork=malloc(sizeof(wnode));
if (!mywork) {
printf("ouch! can't malloc!\n");
break;
}
mywork->jobnum=x;
queue_put(&wq.work,(node *) mywork);
}
pthread_mutex_unlock(&wq.control.mutex);
pthread_cond_broadcast(&wq.control.cond);
printf("sleeping...\n");
sleep(2);
printf("deactivating work queue...\n");
control_deactivate(&wq.control);
/* CLEANUP */
join_threads();
cleanup_structs();
}
|
代碼初排
現(xiàn)在來快速初排代碼。定義的第一個(gè)結(jié)構(gòu)稱作 "wq",它包含了 data_control 和隊(duì)列頭。data_control 結(jié)構(gòu)用于仲裁對(duì)整個(gè)隊(duì)列的訪問,包括隊(duì)列中的節(jié)點(diǎn)。下一步工作是定義實(shí)際的工作節(jié)點(diǎn)。要使代碼符合本文中的示例,此處所包含的都是作業(yè)號(hào)。
接著,創(chuàng)建清除隊(duì)列。注釋說明了它的工作方式。好,現(xiàn)在讓我們跳過 threadfunc()、join_threads()、create_threads() 和 initialize_structs() 調(diào)用,直接跳到 main()。所做的第一件事就是初始化結(jié)構(gòu) -- 這包括初始化 data_controls 和隊(duì)列,以及激活工作隊(duì)列。
有關(guān)清除的注意事項(xiàng)
現(xiàn)在初始化線程。如果看一下 create_threads() 調(diào)用,似乎一切正常 -- 除了一件事。請(qǐng)注意,我們正在分配清除節(jié)點(diǎn),以及初始化它的線程號(hào)和 TID 組件。我們還將清除節(jié)點(diǎn)作為初始自變量傳遞給每一個(gè)新的工作程序線程。為什么這樣做?
因?yàn)楫?dāng)某個(gè)工作程序線程退出時(shí),它會(huì)將其清除節(jié)點(diǎn)連接到清除隊(duì)列,然后終止。那時(shí),主線程會(huì)在清除隊(duì)列中檢測(cè)到這個(gè)節(jié)點(diǎn)(利用條件變量),并將這個(gè)節(jié)點(diǎn)移出隊(duì)列。因?yàn)?TID(線程標(biāo)識(shí))存儲(chǔ)在清除節(jié)點(diǎn)中,所以主線程可以確切知道哪個(gè)線程已終止了。然后,主線程將調(diào)用 pthread_join(tid),并聯(lián)接適當(dāng)?shù)墓ぷ鞒绦蚓€程。如果沒有做記錄,那么主線程就需要按任意順序聯(lián)接工作程序線程,可能是按它們的創(chuàng)建順序。由于線程不一定按此順序終止,那么主線程可能會(huì)在已經(jīng)聯(lián)接了十個(gè)線程時(shí),等待聯(lián)接另一個(gè)線程。您能理解這種設(shè)計(jì)決策是如何使關(guān)閉代碼加速的嗎(尤其在使用幾百個(gè)工作程序線程的情況下)?
創(chuàng)建工作
我們已啟動(dòng)了工作程序線程(它們已經(jīng)完成了執(zhí)行 threadfunc(),稍后將討論此函數(shù)),現(xiàn)在主線程開始將工作節(jié)點(diǎn)插入工作隊(duì)列。首先,它鎖定 wq 的控制互斥對(duì)象,然后分配 16000 個(gè)工作包,將它們逐個(gè)插入隊(duì)列。完成之后,將調(diào)用 pthread_cond_broadcast(),于是所有正在睡眠的線程會(huì)被喚醒,并開始執(zhí)行工作。此時(shí),主線程將睡眠兩秒鐘,然后釋放工作隊(duì)列,并通知工作程序線程終止活動(dòng)。接著,主線程會(huì)調(diào)用 join_threads() 函數(shù)來清除所有工作程序線程。
threadfunc()
現(xiàn)在來討論 threadfunc(),這是所有工作程序線程都要執(zhí)行的代碼。當(dāng)工作程序線程啟動(dòng)時(shí),它會(huì)立即鎖定工作隊(duì)列互斥對(duì)象,獲取一個(gè)工作節(jié)點(diǎn)(如果有的話),然后對(duì)它進(jìn)行處理。如果沒有工作,則調(diào)用 pthread_cond_wait()。您會(huì)注意到這個(gè)調(diào)用在一個(gè)非常緊湊的 while() 循環(huán)中,這是非常重要的。當(dāng)從 pthread_cond_wait() 調(diào)用中蘇醒時(shí),決不能認(rèn)為條件肯定發(fā)生了 -- 它 可能發(fā)生了,也可能沒有發(fā)生。如果發(fā)生了這種情況,即錯(cuò)誤地喚醒了線程,而列表是空的,那么 while 循環(huán)將再次調(diào)用 pthread_cond_wait()。
如果有一個(gè)工作節(jié)點(diǎn),那么我們只打印它的作業(yè)號(hào),釋放它并退出。然而,實(shí)際代碼會(huì)執(zhí)行一些更實(shí)質(zhì)性的操作。在 while() 循環(huán)結(jié)尾,我們鎖定了互斥對(duì)象,以便檢查 active 變量,以及在循環(huán)頂部檢查新的工作節(jié)點(diǎn)。如果執(zhí)行完此代碼,就會(huì)發(fā)現(xiàn)如果 wq.control.active 是 0,while 循環(huán)就會(huì)終止,并會(huì)執(zhí)行 threadfunc() 結(jié)尾處的清除代碼。
工作程序線程的清除代碼部件非常有趣。首先,由于 pthread_cond_wait() 返回了鎖定的互斥對(duì)象,它會(huì)對(duì) work_queue 解鎖。然后,它鎖定清除隊(duì)列,添加清除代碼(包含了 TID,主線程將使用此 TID 來調(diào)用 pthread_join()),然后再對(duì)清除隊(duì)列解鎖。此后,它發(fā)信號(hào)給所有 cq 等待者 (pthread_cond_signal(&cq.control.cond)),于是主線程就知道有一個(gè)待處理的新節(jié)點(diǎn)。我們不使用 pthread_cond_broadcast(),因?yàn)闆]有這個(gè)必要 -- 只有一個(gè)線程(主線程)在等待清除隊(duì)列中的新節(jié)點(diǎn)。當(dāng)它調(diào)用 join_threads() 時(shí),工作程序線程將打印關(guān)閉消息,然后終止,等待主線程發(fā)出的 pthread_join() 調(diào)用。
join_threads()
如果要查看關(guān)于如何使用條件變量的簡(jiǎn)單示例,請(qǐng)參考 join_threads() 函數(shù)。如果還有工作程序線程,join_threads() 會(huì)一直執(zhí)行,等待清除隊(duì)列中新的清除節(jié)點(diǎn)。如果有新節(jié)點(diǎn),我們會(huì)將此節(jié)點(diǎn)移出隊(duì)列、對(duì)清除隊(duì)列解鎖(從而使工作程序可以添加清除節(jié)點(diǎn))、聯(lián)接新的工作程序線程(使用存儲(chǔ)在清除節(jié)點(diǎn)中的 TID)、釋放清除節(jié)點(diǎn)、減少“現(xiàn)有”線程的數(shù)量,然后繼續(xù)。
結(jié)束語(yǔ)
現(xiàn)在已經(jīng)到了“POSIX 線程詳解”系列的尾聲,希望您已經(jīng)準(zhǔn)備好開始將多線程代碼添加到您自己的應(yīng)用程序中。有關(guān)詳細(xì)信息,請(qǐng)參閱 參考資料部分,這部分內(nèi)容還包含了本文中使用的所有源碼的 tar 文件。下一個(gè)系列中再見!
參考資料
關(guān)于作者
 |
|
 |
Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的總裁兼 CEO,Gentoo 項(xiàng)目的總設(shè)計(jì)師,MacMillan 出版書籍的撰稿作者,他的著作有: Caldera OpenLinux Unleashed, SuSE Linux Unleashed, 和 Samba Unleashed。Daniel 自二年級(jí)起就與計(jì)算機(jī)某些領(lǐng)域結(jié)下不解之緣,那時(shí)他首先接觸的是 Logo 程序語(yǔ)言,并沉溺于 Pac-Man 游戲中。這也許就是他至今仍擔(dān)任 SONY Electronic Publishing/Psygnosis 的首席圖形設(shè)計(jì)師的原因所在。Daniel 喜歡與妻子 Mary 和新出生的女兒 Hadassah 一起共度時(shí)光。可通過 drobbins@gentoo.org與 Daniel 聯(lián)系。
|
 |
Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的總裁兼 CEO,Gentoo 項(xiàng)目的總設(shè)計(jì)師,MacMillan 出版書籍的撰稿作者,他的著作有: Caldera OpenLinux Unleashed, SuSE Linux Unleashed, 和 Samba Unleashed。Daniel 自二年級(jí)起就與計(jì)算機(jī)某些領(lǐng)域結(jié)下不解之緣,那時(shí)他首先接觸的是 Logo 程序語(yǔ)言,并沉溺于 Pac-Man 游戲中。這也許就是他至今仍擔(dān)任 SONY Electronic Publishing/Psygnosis 的首席圖形設(shè)計(jì)師的原因所在。Daniel 喜歡與妻子 Mary 和新出生的女兒 Hadassah 一起共度時(shí)光。可通過 drobbins@gentoo.org與 Daniel 聯(lián)系。
|
 |
Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的總裁兼 CEO,Gentoo 項(xiàng)目的總設(shè)計(jì)師,MacMillan 出版書籍的撰稿作者,他的著作有: Caldera OpenLinux Unleashed, SuSE Linux Unleashed, 和 Samba Unleashed。Daniel 自二年級(jí)起就與計(jì)算機(jī)某些領(lǐng)域結(jié)下不解之緣,那時(shí)他首先接觸的是 Logo 程序語(yǔ)言,并沉溺于 Pac-Man 游戲中。這也許就是他至今仍擔(dān)任 SONY Electronic Publishing/Psygnosis 的首席圖形設(shè)計(jì)師的原因所在。Daniel 喜歡與妻子 Mary 和新出生的女兒 Hadassah 一起共度時(shí)光。可通過 drobbins@gentoo.org與 Daniel 聯(lián)系。
|
Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的總裁兼 CEO,Gentoo 項(xiàng)目的總設(shè)計(jì)師,MacMillan 出版書籍的撰稿作者,他的著作有: Caldera OpenLinux Unleashed, SuSE Linux Unleashed, 和 Samba Unleashed。Daniel 自二年級(jí)起就與計(jì)算機(jī)某些領(lǐng)域結(jié)下不解之緣,那時(shí)他首先接觸的是 Logo 程序語(yǔ)言,并沉溺于 Pac-Man 游戲中。這也許就是他至今仍擔(dān)任 SONY Electronic Publishing/Psygnosis 的首席圖形設(shè)計(jì)師的原因所在。Daniel 喜歡與妻子 Mary 和新出生的女兒 Hadassah 一起共度時(shí)光。可通過 drobbins@gentoo.org與 Daniel 聯(lián)系。