第四節(jié):事件對象(Event Objects)
本節(jié)介紹如下內(nèi)容:
1. 同步與異步
2. 為何需要同步
3. 什么是事件對象(Event Object)
4. 事件對象類接口定義
5. 示例程序
6. 事件對象類的UNIX和Windows實(shí)現(xiàn)
1. 同步(Synchronization)與異步(Asynchronization)
首先對同步與異步的概念做一個簡單的說明。
當(dāng)程序1調(diào)用程序2時(shí),程序1停下不動,直到程序2完成回到程序1來,程序1才繼續(xù)去, 這就是所謂的同步。如果程序1調(diào)用程序2后,徑自繼續(xù)自己的下一個動作,那么兩者之就是所謂的異步。
舉個例子,在WIN32
API中,SendMessage()就是同步行為,而PostMessage()就是異步行 為。在Windows系統(tǒng)中,PostMessage()是把消息放到對方的消息隊(duì)列中,然后回到原調(diào)用 點(diǎn)繼續(xù)執(zhí)行,所以這就是異步(asynchronous)行為。而SendMessage()根本就像是“直 接調(diào)用窗口的窗口函數(shù)”,直到該窗口函數(shù)結(jié)束,然后才回到原調(diào)用點(diǎn),所以它是同步( synchronous)行為。
2. 為何需要同步
撰寫多線程程序的一個最具挑戰(zhàn)性的問題就是:如何讓一個線程和另一個線程合作。除非 你讓他們同心協(xié)力,否則必然會出現(xiàn)如第三節(jié)所說的競爭條件(race condition)和數(shù)據(jù) 被破壞(data
corruption)的情況。
當(dāng)多個線程共享同一內(nèi)存區(qū)域的時(shí)候,我們需要確保每一個線程所看到的數(shù)據(jù)的一致性。假如對于每一個線程所使用的變量來說,其它任何線程都不會讀取或使用該 變量,那么根 本不存在數(shù)據(jù)一致性的問題。同樣地,對于一個有著只讀屬性的變量來說,多個線程同時(shí) 讀取它的值的話,也不會有數(shù)據(jù)一致性的問題存在。然而,當(dāng)一個線程可以修改一個變量 ,同時(shí)其它線程也能夠讀取或修改該變量的話,我們就需要同步這些線程,以確保每一個 線程在訪問該變量的內(nèi)存內(nèi)容時(shí)所用到的值是有效的。
舉個例子,假設(shè)有一塊未初始化的內(nèi)存塊和兩個線程,一個讀線程,一個寫線程。我們應(yīng) 該保證讀線程在讀取該內(nèi)存塊時(shí),它已經(jīng)被寫線程初始化好了,否則讀線程只能讀到一塊 未初始化完成的無效數(shù)據(jù)。這就需要用到線程的同步機(jī)制(synchronous
mechanism)。 線程間的協(xié)調(diào)工作是由同步機(jī)制來完成的。同步機(jī)制相當(dāng)于線程之間的紅綠燈。程序員可 以設(shè)計(jì)讓一組線程使用同一個紅綠燈系統(tǒng)。這個紅綠燈系統(tǒng)負(fù)責(zé)給某個線程綠燈而給其他 線程紅燈。這一組紅綠燈系統(tǒng)必須確保每一個線程都有機(jī)會獲得綠燈。 有好多種同步機(jī)制可以運(yùn)用。使用哪一種完全視欲解決的問題而定。這些同步機(jī)制常常以 各種方式組合在一起,以產(chǎn)生出更精密的機(jī)制。
3. 什么是事件對象(Event Object)
事件對象(Event
Object)是一種最具彈性的同步機(jī)制,它的唯一目的就是成為激發(fā)(
Signaled)狀態(tài)或未激發(fā)(Unsignaled)狀態(tài)。這兩種狀態(tài)完全由程序控制。
我們通過上面介紹的讀寫線程的例子來說明事件對象的激發(fā)狀態(tài)和未激發(fā)狀態(tài)的含義。讀 線程和寫線程擁有同一個事件對象。該事件對象的初始狀態(tài)為非激發(fā)狀態(tài)。當(dāng)讀線程需要 讀共享的那塊內(nèi)存時(shí),它需要判斷該事件對象的狀態(tài)。如果該事件對象處于非激發(fā)狀態(tài),
則讀線程等待,直到該事件對象處于激發(fā)狀態(tài)為止。寫線程會在那塊共享的內(nèi)存被初始化 好之后將該事件對象的狀態(tài)設(shè)為激發(fā)狀態(tài)。這時(shí)讀線程得知了該事件對象的狀態(tài)已經(jīng)由非 激發(fā)狀態(tài)變?yōu)榧ぐl(fā)狀態(tài),于是它開始讀取那塊共享的內(nèi)存,并執(zhí)行后續(xù)的操作。 事件對象之所以有大用途,正是因?yàn)樗鼈兊臓顟B(tài)完全在程序員的掌控之下。因此,程序員 可以精確的告訴一個事件對象做什么事,以及什么時(shí)候去做。
事件對象可以分為自動重置的事件對象(Automatic-Reset
Event Object)和手動重置的 事件對象(Manual-Reset Event Object)。自動重置的事件對象會在事件對象變成激發(fā) 狀態(tài)(因而喚醒一個線程)之后,自動重置為非激發(fā)狀態(tài)。而手動重置的事件對象,不會 自動重置,必須靠程序操作才能將激發(fā)狀態(tài)的事件對象重置為非激發(fā)狀態(tài)。 事件對象所能完成的一切功能都可以通過互斥來完成。下面我們通過比較使用事件對象來 實(shí)現(xiàn)讀寫線程的例子和使用互斥來實(shí)現(xiàn)讀寫線程的例子,以說明事件對象的作用和它存在 的必要性。
例一:使用事件對象來實(shí)現(xiàn)讀寫線程
void threadRead(事件對象類型 *事件對象)
{
阻塞事件對象;
讀取共享內(nèi)存的數(shù)據(jù);
}
void threadWrite(事件對象類型 *事件對象)
{
將適當(dāng)?shù)臄?shù)據(jù)寫入共享內(nèi)存;
激發(fā)事件對象;
}
例二:使用互斥來實(shí)現(xiàn)讀寫線程
bool globalIsWritten = false;
void threadRead(通行證類型 *通行證)
{
獲取通行證;
while (!globalIsWritten)
{
歸還通行證;
sleep(sometime);
獲取通行證;
}
歸還通行證;
讀取共享內(nèi)存的數(shù)據(jù);
}
void threadWrite(通行證類型 *通行證)
{
將適當(dāng)?shù)臄?shù)據(jù)寫入共享內(nèi)存;
獲取通行證;
globalIsWritten = true;
歸還通行證;
}
很明顯,使用事件對象來實(shí)現(xiàn)讀寫線程的代碼要比使用互斥來實(shí)現(xiàn)讀寫線程的代碼優(yōu)雅許 多。使用事件對象來實(shí)現(xiàn)讀寫線程的代碼顯得更加干凈整潔,而且可讀性更高。使用互斥 來實(shí)現(xiàn)讀寫線程時(shí),在讀線程中,需要輪詢地互斥訪問讀寫線程間的共享變量
globalIsWritten,因此其效率一定不如使用事件對象來實(shí)現(xiàn)讀寫線程的效率高。我將后 面的“手動重置的事件對象”的示例程序改為完全使用互斥來實(shí)現(xiàn)后,發(fā)現(xiàn)其運(yùn)行時(shí)間是 使用事件對象來實(shí)現(xiàn)的1.21倍。這個測試結(jié)果和我們的預(yù)期相一致。 因此,對于類似于讀寫線程這樣的例子,事件對象相對于互斥提供了更加優(yōu)雅和高效的解 決方案。
4.
事件對象類接口定義
文件event.h
#ifndef __EVENT_H__
#define __EVENT_H__
#include <windows.h>
class Event
{
public:
Event(bool bManualUnsignal, bool bSignaled);
virtual ~Event();
virtual bool block();
virtual bool signal();
virtual bool unsignal();
private:
// 依賴于具體實(shí)現(xiàn),后面再說。
};
#endif
其中,
Event::Event(bool bManualUnsignal, bool bSignaled),事件對象類的構(gòu)造函數(shù)。
bManualUnsignal用于指定事件對象的類型。如果其值為true,則該事件對象是手動重置 的事件對象;如果其值為false,則該事件對象是自動重置的事件對象。bSignaled用于指 定事件對象的初始狀態(tài)。如果其值為true,則該事件對象的初始狀態(tài)為激發(fā)狀態(tài);如果其 值為false,則該事件對象的初始狀態(tài)為非激發(fā)狀態(tài)。
Event::~Event(),事件對象類的析構(gòu)函數(shù)。用于摧毀事件對象。
Event::block(),根據(jù)事件對象的狀態(tài),對擁有該事件對象的線程進(jìn)行控制。如果事件對 象處于非激發(fā)狀態(tài),則擁有該事件對象的線程開始等待,直到該事件對象的狀態(tài)變?yōu)榧ぐl(fā) 狀態(tài)。如果事件對象處于激發(fā)狀態(tài)或者當(dāng)事件對象的狀態(tài)由非激發(fā)狀態(tài)變?yōu)榧ぐl(fā)狀態(tài)的時(shí) 候,首先判斷該事件對象是那種類型的,如果該事件對象是自動重置的,那么需要將該事 件對象的狀態(tài)設(shè)為非激發(fā)狀態(tài),然后喚醒等待該事件對象的線程。
Event::signal(),將事件對象的狀態(tài)設(shè)為激發(fā)狀態(tài)。如果事件對象是手動重置的事件對 象,那么該事件對象會一直保持激發(fā)狀態(tài),直到Event::unsignal()被調(diào)用,該事件對象 才會由激發(fā)狀態(tài)變?yōu)榉羌ぐl(fā)狀態(tài)。在手動設(shè)置的事件對象保持激發(fā)狀態(tài)的時(shí)候,所有等待 該事件對象的線程都將被喚醒。如果事件對象是自動重置的事件對象,那么該事件對象會 一直保持激發(fā)狀態(tài),直到一個等待該事件對象的線程被喚醒,這時(shí)該事件對象會由激發(fā)狀 態(tài)變?yōu)榉羌ぐl(fā)狀態(tài)(由Event::block()來完成)。
Event::unsignal(),將事件對象的狀態(tài)設(shè)為非激發(fā)狀態(tài)。該方法主要用于手動重置的事 件對象,它必須顯式地調(diào)用該方法以使得自己的狀態(tài)變?yōu)榉羌ぐl(fā)狀態(tài)。而對于自動重置的 事件對象來說,當(dāng)一個等待線程被喚醒時(shí),它會自動地將自己的狀態(tài)由激發(fā)狀態(tài)變?yōu)榉羌?發(fā)狀態(tài)。
在Windows操作系統(tǒng)中,還有一種對事件對象的操作,叫做PulseEvent()。在我們的事件 對象模型中并沒有引入該接口,因?yàn)镻ulseEvent()是一個不穩(wěn)定的操作。Windows只是為 了向后兼容才保留了PulseEvent()。
下面對PulseEvent()函數(shù)做一個簡單的介紹,并且說明為什么該操作不穩(wěn)定。
如果一個事件對象是手動重置的,那么對該事件對象進(jìn)行PulseEvent()操作后,該事件對 象會被設(shè)為激發(fā)狀態(tài),所有的等待該事件對象的線程都會被喚醒,之后該事件對象恢復(fù)為 非激發(fā)狀態(tài)。如果一個事件對象是自動重置的,那么對該事件對象進(jìn)行PulseEvent()操作 后,該事件對象會被設(shè)為激發(fā)狀態(tài),一個等待該事件對象的線程會被喚醒,之后該事件對 象恢復(fù)為非激發(fā)狀態(tài)。
注意,如果沒有任何線程在等待事件對象(不管是手動重置的還是自動重置的),或者沒 有任何線程可以立即被喚醒的話,對該事件對象進(jìn)行PulseEvent()操作后,唯一的結(jié)果是 該事件對象的狀態(tài)被設(shè)置為非激發(fā)狀態(tài)。在這種情況下,這個事件對象會被遺失。這時(shí),可能會引起死鎖。
舉個例子,假設(shè)一個程序由兩個線程(線程A和線程B)組成。線程A累加一個計(jì)數(shù)器,
后調(diào)用Event::block()等待一個事件對象。如果在這兩個操作之間發(fā)生了上下文切換(
context switch),線程B開始執(zhí)行,它檢查計(jì)數(shù)器內(nèi)容然后對著同一個事件對象進(jìn)行
PulseEvent()操作。這時(shí)候這個要求蘇醒的請求會被遺失掉。而線程A會因?yàn)樗却氖?件對象永遠(yuǎn)不會被設(shè)置為激發(fā)狀態(tài)而永遠(yuǎn)等待下去,程序進(jìn)入死鎖狀態(tài)。這時(shí),線程A被 稱作饑餓線程。
因此,PulseEvent()是一個不穩(wěn)定的操作,在我們的事件對象模型中將不包括該操作。
5.
示例程序
自動重置的事件對象
文件common.h
#ifndef __COMMON_H__
#define __COMMON_H__
struct Param
{
long threadID;
int *count;
};
const int TCOUNT = 10;
const int COUNT_LIMIT = 12;
#endif
文件watchcount.h
#ifndef __WATCH_COUNT_H__
#define __WATCH_COUNT_H__
#include "thread.h"
class Event;
class Mutex;
class WatchCount : public Thread
{
public:
WatchCount(Event& e, Mutex& m);
protected:
void* run(void *param);
private:
Event& event;
Mutex& mutex;
};
#endif
文件watchcount.cpp
#include "watchcount.h"
#include "common.h"
#include "mutex.h"
#include "event.h"
#include <iostream>
using std::cout;
using std::endl;
WatchCount::WatchCount(Event& e, Mutex& m) : event(e), mutex(m)
{
}
void* WatchCount::run(void *param)
{
Param *prm = static_cast<Param *>(param);
long id = prm->threadID;
int *count = prm->count;
mutex.acquire();
cout << "Starting WatchCount: thread "
<< id
<< "."
<< endl;
cout << "WatchCount: thread "
<< id
<< " going into wait..."
<< endl;
mutex.release();
event.block();
mutex.acquire();
cout << "WatchCount: thread "
<< id
<< " Event signaled."
<< endl;
*count += 125;
cout << "WatchCount: thread "
<< id
<< " count now = "
<< *count
<< "."
<< endl;
mutex.release();
return NULL;
}
文件inccount.h
#ifndef __INC_COUNT_H__
#define __INC_COUNT_H__
#include "thread.h"
class Event;
class Mutex;
class IncCount : public Thread
{
public:
IncCount(Event& e, Mutex& m);
protected:
void* run(void *param);
private:
Event& event;
Mutex& mutex;
};
#endif
文件inccount.cpp
#include "inccount.h"
#include "common.h"
#include "mutex.h"
#include "event.h"
#include <iostream>
using std::cout;
using std::endl;
IncCount::IncCount(Event& e, Mutex& m) : event(e), mutex(m)
{
}
void* IncCount::run(void *param)
{
Param *prm = static_cast<Param *>(param);
long id = prm->threadID;
int *count = prm->count;
for (int i = 0; i < TCOUNT; ++i)
{
mutex.acquire();
++(*count);
/*
* Check the value of count and signal waiting
thread when condition
is
* reached.
*/
if (*count == COUNT_LIMIT)
{
cout << "IntCount: thread
"
<< id
<< ",
count = "
<< *count
<< "
Threshold reached. ";
event.signal();
cout << "Just sent
signal."
<< endl;
}
cout << "IncCount: thread "
<< id
<< ", count = "
<< *count
<< ", unlocking
mutex."
<< endl;
mutex.release();
/* Do some work so threads can alternate on mutex
lock */
sleep(1000);
}
return NULL;
}
文件mainautounsignal.cpp
#include "inccount.h"
#include "watchcount.h"
#include "common.h"
#include "mutex.h"
#include "event.h"
#include <iostream>
using std::cout;
using std::endl;
int main(int argc, char* argv[])
{
Event event(false, false);
Mutex mutex;
int count = 0;
Param prm1 = {1, &count};
Param prm2 = {2, &count};
Param prm3 = {3, &count};
WatchCount wc(event, mutex);
IncCount ic1(event, mutex);
IncCount ic2(event, mutex);
wc.start(&prm1);
ic1.start(&prm2);
ic2.start(&prm3);
/* Wait for all thread to complete */
wc.wait();
ic1.wait();
ic2.wait();
cout << "Main(): Waited on 3 thread. Final value of
count = "
<< count
<< ". Done."
<< endl;
return 0;
}
在此示例程序中,主線程創(chuàng)造了三個線程。其中,兩個線程(IncCount)對一個“count ”變量執(zhí)行遞增操作,第三個線程(WatchCount)觀察那個“count”變量的值。當(dāng)“ count”變量達(dá)到一個預(yù)定義的值(COUNT_LIMIT)時(shí),等待線程(WatchCount)被兩個遞 增線程(IncCount)中的一個喚醒。等待線程(WatchCount)被喚醒后會立即修改“ count”變量的值。兩個遞增線程(IncCount)會一直執(zhí)行,直到達(dá)到TCOUNT為止。最后 ,主線程會打印出“count”變量的最終值。
手動重置的事件對象
文件common.h
#ifndef __COMMON_H__
#define __COMMON_H__
#include <string>
using std::string;
struct Param
{
long threadID;
string *data;
};
#endif
文件readfrombuffer.h
#ifndef __READ_FROM_BUFFER_H__
#define __READ_FROM_BUFFER_H__
#include "thread.h"
class Event;
class Mutex;
class ReadFromBuffer : public Thread
{
public:
ReadFromBuffer(Event& e, Mutex& m);
protected:
void* run(void *param);
private:
Event& event;
Mutex& mutex;
};
#endif
文件readfrombuffer.cpp
#include "readfrombuffer.h"
#include "common.h"
#include "event.h"
#include "mutex.h"
#include <iostream>
using std::cout;
using std::endl;
ReadFromBuffer::ReadFromBuffer(Event& e, Mutex& m) : event(e), mutex(m)
{
}
void* ReadFromBuffer::run(void *param)
{
Param *prm = static_cast<Param *>(param);
long id = prm->threadID;
string *data = prm->data;
mutex.acquire();
cout << "ReadFromBuffer: thread "
<< id
<< " waiting for event
signaled..."
<< endl;
mutex.release();
event.block();
mutex.acquire();
cout << "ReadFromBuffer: thread "
<< id
<< " reading from buffer ("
<< *data
<< ")"
<< endl;
mutex.release();
return NULL;
}
文件writetobuffer.h
#ifndef __WRITE_TO_BUFFER__
#define __WRITE_TO_BUFFER__
#include "thread.h"
class Event;
class Mutex;
class WriteToBuffer : public Thread
{
public:
WriteToBuffer(Event& e, Mutex& m);
protected:
void* run(void *param);
private:
Event& event;
Mutex& mutex;
};
#endif
文件writetobuffer.cpp
#include "writetobuffer.h"
#include "common.h"
#include "event.h"
#include "mutex.h"
#include <iostream>
using std::cout;
using std::endl;
WriteToBuffer::WriteToBuffer(Event& e, Mutex& m) : event(e), mutex(m)
{
}
void* WriteToBuffer::run(void *param)
{
Param *prm = static_cast<Param *>(param);
long id = prm->threadID;
string *data = prm->data;
*data = "Hello World!";
mutex.acquire();
cout << "WriteToBuffer: thread "
<< id
<< " writing to the shared
buffer..."
<< endl;
mutex.release();
event.signal();
return NULL;
}
文件mainmanualunsignal.cpp
#include "writetobuffer.h"
#include "readfrombuffer.h"
#include "common.h"
#include "event.h"
#include "mutex.h"
#include <iostream>
using std::cout;
using std::endl;
int main(int argc, char* argv[])
{
Event event(true, false);
Mutex mutex;
string data;
Param prm1 = {1, &data};
Param prm2 = {2, &data};
Param prm3 = {3, &data};
Param prm4 = {4, &data};
Param prm5 = {5, &data};
ReadFromBuffer read1(event, mutex);
ReadFromBuffer read2(event, mutex);
ReadFromBuffer read3(event, mutex);
ReadFromBuffer read4(event, mutex);
WriteToBuffer write(event, mutex);
read1.start(&prm1);
read2.start(&prm2);
read3.start(&prm3);
read4.start(&prm4);
write.start(&prm5);
mutex.acquire();
cout << "Main thread waiting for threads to
exit..."
<< endl;
mutex.release();
read1.wait();
read2.wait();
read3.wait();
read4.wait();
write.wait();
cout << "All threads ended, cleaning up for
application exit..."
<< endl;
return 0;
}
在此示例程序中,主線程創(chuàng)造了五個線程。其中,四個線程(ReadFromBuffer)讀取“ data”變量的內(nèi)容,第五個線程(WriteToBuffer)初始化“data”變量。四個讀線程(
ReadFromBuffer)會在寫線程(WriteToBuffer)完成對“data”變量的初始化之前一直 保持等待狀態(tài)。當(dāng)寫線程(WriteToBuffer)將“data”變量初始化好之后,四個讀線程 (ReadFromBuffer)才會被一一喚醒。最后,主線程會在這四個讀線程(ReadFromBuffer
)和一個寫線程(WriteToBuffer)都執(zhí)行完成后退出,從而結(jié)束整個程序。
6.
事件對象類的UNIX和Windows實(shí)現(xiàn)
UNIX實(shí)現(xiàn)
文件event.h
#ifndef __EVENT_H__
#define __EVENT_H__
#include <pthread.h>
class Event
{
public:
Event(bool bManualUnsignal, bool bSignaled);
virtual ~Event();
virtual bool block();
virtual bool signal();
virtual bool unsignal();
private:
const bool bManUnsig;
pthread_cond_t cv;
pthread_mutex_t mutex;
bool bSig;
};
#endif
文件event.cpp
#include "event.h"
Event::Event(bool bManualUnsignal, bool bSignaled) : bManUnsig(bManualUnsignal
), bSig(bSignaled)
{
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cv, NULL);
}
Event::~Event()
{
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cv);
}
bool Event::block()
{
int ret = 0;
ret += pthread_mutex_lock(&mutex);
if (bSig)
{
if (!bManUnsig)
{
bSig = false;
}
}
else
{
pthread_cond_wait(&cv, &mutex);
if (!bManUnsig)
{
bSig = false;
}
}
ret += pthread_mutex_unlock(&mutex);
return ret == 0;
}
bool Event::signal()
{
int ret = 0;
ret += pthread_mutex_lock(&mutex);
if (!bSig)
{
if (bManUnsig)
{
ret +=
pthread_cond_broadcast(&cv);
}
else
{
ret += pthread_cond_signal(&cv);
}
bSig = true;
}
ret += pthread_mutex_unlock(&mutex);
return ret == 0;
}
bool Event::unsignal()
{
int ret = 0;
ret += pthread_mutex_lock(&mutex);
if (bSig)
{
bSig = false;
}
ret += pthread_mutex_unlock(&mutex);
return ret == 0;
}
Windows實(shí)現(xiàn)
文件event.h
#ifndef __EVENT_H__
#define __EVENT_H__
#include <windows.h>
class Event
{
public:
Event(bool bManualUnsignal, bool bSignaled);
virtual ~Event();
virtual bool block();
virtual bool signal();
virtual bool unsignal();
private:
HANDLE handle;
};
#endif
文件event.cpp
#include "event.h"
Event::Event(bool bManualUnsignal, bool bSignaled)
{
handle = CreateEvent(NULL, bManualUnsignal, bSignaled, NULL);
}
Event::~Event()
{
CloseHandle(handle);
}
bool Event::block()
{
return WaitForSingleObject(handle, INFINITE) == WAIT_OBJECT_0;
}
bool Event::signal()
{
return SetEvent(handle) == TRUE;
}
bool Event::unsignal()
{
return ResetEvent(handle) == TRUE;
}
小結(jié)
本節(jié)首先介紹了同步與異步的基本概念,進(jìn)而說明了同步在多線程編程中的作用。
事件對象(Event
Object)是一種最具彈性的同步機(jī)制。事件對象在某些條件滿足之前將 一直保持非激發(fā)狀態(tài)。程序員可以完全控制事件對象的狀態(tài)(激發(fā)狀態(tài)和非激發(fā)狀態(tài))。 事件對象使得程序員可以以最大的靈活性來定義復(fù)雜的同步對象。有兩種類型的事件對象 (自動重置的事件對象和手動重置的事件對象)。一個手動重置的事件對象需要程序員顯 式地將其狀態(tài)從激發(fā)狀態(tài)返回到非激發(fā)狀態(tài)。然而一個自動重置的事件對象會在一個
Event::block()操作完成后自動地返回到非激發(fā)狀態(tài)。 雖然事件對象所能完成的一切功能都可以通過互斥來完成,但是使用事件對象的解決方案 顯得更加優(yōu)雅,并且效率更高。