第三節(jié) 線程互斥
本節(jié)介紹如下內(nèi)容
1.
主動對象
2.
調(diào)度與原子操作
3.
競爭條件和數(shù)據(jù)一致性
4.
為何需要互斥
5.
互斥類接口定義
6.
示例程序
7.
互斥類的Unix和Windows實現(xiàn)
1. 主動對象(Active Object)
第二節(jié)介紹Thread類的時候曾經(jīng)提到,每個子線程一旦被創(chuàng)建,就被賦予了自己的生命。當(dāng)一個線程類創(chuàng)建并啟動之后,它將會以自己的步調(diào)主動執(zhí)行其獨立線程,它和其他線程(包括主線程)的執(zhí)行是并行關(guān)系。
為了詳細(xì)說明這個問題,先介紹一下什么是控制流(control
flow)。計算機(jī)科學(xué)中,控制流指指令式(imperative)或函數(shù)式(functional)編程語言中語句、指令、函
數(shù)調(diào)用的執(zhí)行(或求值)序列。
單線程程序中,控制流始終是一線串珠,一旦控制流達(dá)到某個對象,由程序主動調(diào)用對象函數(shù),在函數(shù)執(zhí)行完畢后,控制流返回主程序繼續(xù)執(zhí)行。對于被訪問對象來 說,訪問、修改、成員函數(shù)調(diào)用都是一個被動的過程,此過程受控于該程序的控制流。所以,稱單線程程序的對象為被動對象(Passive Object)。
與被動對象相對應(yīng)的是主動對象。主動對象和被動對象的區(qū)別在于,主動對象可以發(fā)起自己的線程,創(chuàng)建新的執(zhí)行序列,產(chǎn)生獨立于主線程的控制流分支。簡而言 之,主動對象可以獨立于主線程,自主制定自己的控制流。如果愿意,你可以實現(xiàn)一個和主線程沒有任何協(xié)調(diào)關(guān)系,天馬行空式的獨立線程。當(dāng)然,這樣的線程往往 也意味著毫無用處(娛樂作用除外)。
2. 調(diào)度和原子操作
從理論模型上說,多線程環(huán)境中,線程之間是獨立、對等關(guān)系。一個線程完全可以無視其他線程的存在。但實際多線程執(zhí)行環(huán)境中,線程數(shù)往往遠(yuǎn)大于CPU數(shù)量。 為了共享和分配資源,線程必需遵守一定規(guī)則,進(jìn)行必要的協(xié)調(diào)。操作系統(tǒng)中具體的規(guī)則執(zhí)行者是調(diào)度程序。因此,要想掌握多線程編程,就必需了解線程調(diào)度的基 本概念和特點。在此基礎(chǔ)上,能了解為什么需要在線程之間進(jìn)行協(xié)調(diào),進(jìn)而才能透徹理解如何協(xié)調(diào)多線程的并發(fā)執(zhí)行。
現(xiàn)代操作系統(tǒng)中,存在許多不同的線程調(diào)度模型,這些調(diào)度模型的基本共同點就是線程執(zhí)行順序具有隨機(jī)性和不確定性。調(diào)度模型既無法事先知道某一時刻會存在多 少線程,也無法知道哪個線程會正在運行,甚至也不知道某個線程確切會到什么時刻執(zhí)行結(jié)束,更不用說預(yù)先安排在特定時刻執(zhí)行特定線程的特定語句。
前面提到,控制流就是語句,指令或函數(shù)調(diào)用的順序序列。反映到時間軸上,就是一系列離散分布的執(zhí)行序列。線程調(diào)度作用于多個控制流的客觀效果,就是多個控 制流按同一條時間軸排列時,是一個近似隨機(jī)的執(zhí)行序列;按每個控制流各自的時間軸排列時,則是一個具有先后順序的執(zhí)行序列。
在具體的程序執(zhí)行上,這個特點就表現(xiàn)為線程A的一個語句執(zhí)行完畢后,緊接著執(zhí)行的可能是另一個線程B的一個語句。甚至可能是線程A的一個語句還沒有執(zhí)行完畢,就接著執(zhí)行線程B的語句。
對于后一點不用感到奇怪。因為操作系統(tǒng)中,調(diào)度程序作為系統(tǒng)底層實現(xiàn)的一部分,參與調(diào)度的操作指令可能比高級編程語言的基本語句要底層的多。同一個調(diào)度程 序,其調(diào)度的眾多線程可能由多種高級語言編寫,所以調(diào)度程序基本上不可能以某種高級編程語言的單條語句為單位安排執(zhí)行序列。
通常,一條高級語言語句可能對應(yīng)多條匯編指令,而一條匯編指令可能對應(yīng)多條CPU微碼指令。而一條微碼指令,則可能對應(yīng)邏輯電路的一個具體電路邏輯。順便 提一下,這也是Verilog, VHDL等高級語言能夠綜合出具體的邏輯電路的基本原理。所不同的是,高級語言編譯器編譯的最終單位是匯編,而硬件描述語言綜合的最終單位是和微碼對應(yīng)的 電路器件單元(集成電路前端設(shè)計的內(nèi)容。記得以前暑期實習(xí),我窩在學(xué)校做的就是這么一個元件庫,做的很不像樣子居然還得到了一定的肯定,多年后想起來還是 汗顏)。
至于系統(tǒng)調(diào)度程序具體會定義怎樣的原子操作集合,會以什么粒度的指令為調(diào)度基本單位,這就是系統(tǒng)設(shè)計者各顯神通的地方了。個人認(rèn)為,作為高級語言的編程 者,記住什么操作是原子操作意義并不明顯。大多數(shù)場合,只要認(rèn)為,多線程的控制流在同一條時間軸上看來是完全隨機(jī)的就可以了。要記住,墨菲定律生效的時 候,看似不可能的事情都能成為現(xiàn)實。
多線程程序設(shè)計中,一種樸素的設(shè)計思想是將線程具體化為主動對象,主動對象之間通過共享環(huán)境(全局資源)來維護(hù)當(dāng)前應(yīng)用的運行狀態(tài);主動對象間通過一套約 定的互斥和同步規(guī)則來實現(xiàn)通信;線程的具體執(zhí)行時序則由調(diào)度程序安排。主動對象之間,應(yīng)盡量避免一個主動對象直接調(diào)用另一個主動對象的內(nèi)部方法(例如 suspend, stop, exit)直接控制另一個線程的執(zhí)行步調(diào)。主動對象的行為應(yīng)該完全由其本身控制,和其他主動對象的交互盡量只通過環(huán)境以及同步語句來實現(xiàn)。
實際實現(xiàn)中,如果多個主動對象都可以直接調(diào)用某個主動對象的stop方法終止其運行,這個程序是非常脆弱的,因為在調(diào)度程序之外,不借助同步機(jī)制,主觀假 定線程執(zhí)行時序,人為更改線程執(zhí)行狀態(tài)是非常不明智的。即使這樣的程序可用,對于維護(hù)者來說其難度也不亞于維護(hù)goto語句。
這也就是前一篇文章所定義線程類中detach, start, stop沒有加鎖的原因,因為并不希望在多個線程中調(diào)用同一個線程對象的這些方法。
3.
競爭條件和數(shù)據(jù)一致性
共享環(huán)境是進(jìn)程全局資源的同義詞。在多線程并發(fā)執(zhí)行中,最常遇到的問題就是共享環(huán)境被污染。具體體現(xiàn)就是全局?jǐn)?shù)據(jù)被破壞,全局文件內(nèi)容被破壞 …
。
例如:
有一個64位全局變量long globalVar = 0;主動對象A想把它置為0x000A000B000C000D;假設(shè)這個操作分兩步執(zhí)行,先將高32位置為000A000B,再把低32位置為
000C000D。但不巧的是,對象A剛剛將高位置位,調(diào)度程序安排另一主動對象B執(zhí)行。這時,全局變量globalVar內(nèi)部的值是一個非法值,它是無 意義的。在B拿著這個值做進(jìn)一步處理時,得出的也是錯誤結(jié)果。這時,稱為數(shù)據(jù)的一致性被破壞。
線程調(diào)度的不確定性也可能導(dǎo)致程序執(zhí)行結(jié)果的不確定性。有時候這種不確定性會導(dǎo)致程序得到錯誤的運行結(jié)果。
例如:
為了對Thread類所生成的對象總數(shù)計數(shù),定義一個全局變量Unsigned int counter = 0; 在Thread類的構(gòu)造函數(shù)中,執(zhí)行++counter?,F(xiàn)在假設(shè)有2個Thread對象objA和objB并發(fā)運行,考慮如下兩個場景:
Scenario1.操作序列如下:
1. counter = 0;
2. objA將counter值0從內(nèi)存讀入寄存器A;
3. objA將寄存器A值加1;
4. objA將寄存器A值寫回內(nèi)存,counter值為1;
5. objB將counter值1從內(nèi)存讀入寄存器B;
6. objB將寄存器B值加1;
7. objA將寄存器B值寫回內(nèi)存,counter值為2;
8. 最終counter值為2。
Scenario2.操作序列如下:
1. counter = 0;
2. objA將counter值0從內(nèi)存讀入寄存器A;
3. objB將counter值0從內(nèi)存讀入寄存器B
4. objA將寄存器A值加1;
5. objB將寄存器B值加1;
6. objA將寄存器A值寫回內(nèi)存,counter值為1;
7. objA將寄存器B值寫回內(nèi)存,counter值為1;
8. 最終counter值為1。
場景1的結(jié)果是設(shè)計的本意,場景2的結(jié)果對我們而言是錯誤的。
一個線程的執(zhí)行結(jié)果正確性取決于于其他線程執(zhí)行時序的條件,稱為競爭條件。中文這樣翻譯不倫不類,但從英文字面理解非常容易。Race一般是計時賽,某位選手跑得快一點,競賽結(jié)果就有變化,最終結(jié)果由race condition決定,還是非常形象的。
4. 為何需要互斥
線程間協(xié)作問題,通??梢詺w為互斥和同步兩類。其中互斥又主要解決兩類問題:維護(hù)數(shù)據(jù)一致性、避免競爭條件的出現(xiàn)。
解決一致性問題,通俗說就是,修改數(shù)據(jù)的線程通告其他線程“我正在修改你要訪問的對象X,操作過程中不能保證這個數(shù)據(jù)的有效性,請不要使用此對象”。
避免競爭條件,通俗說就是,某線程通告其他線程“我將要進(jìn)行涉及某對象的一系列操作A,在我未完成這一系列操作之前,如果有人和我同時執(zhí)行涉及此對象的操作序列B(B也可能就是A),將會影響我執(zhí)行結(jié)果的正確性,請不要進(jìn)行涉及此對象的操作”。
這種操作序列A有時候也被稱為“原子性操作”,因為它不允許操作序列B在時間軸上和它交叉分布,必需保證在時間軸上看來,操作序列A是一個不可分割的整體。(物理學(xué)早期,原子也被認(rèn)為是不可分割的)。
以上冗長的解釋,精簡成一句話,就是“線程間需要互斥執(zhí)行”。需要互斥的操作對應(yīng)的代碼也有一個很學(xué)術(shù)的名稱-“關(guān)鍵域(或關(guān)鍵區(qū))”。
那么如何實現(xiàn)互斥呢?一個簡單的思路就是,設(shè)立一個權(quán)威仲裁者,給那些需要互斥執(zhí)行的線程頒發(fā)一個共用的通行證。某個線程想要進(jìn)入一個關(guān)鍵域執(zhí)行,需要先 申請可以進(jìn)入該關(guān)鍵域的通行證,如果別的線程已經(jīng)拿走了該通行證,則本線程等待,進(jìn)入休眠狀態(tài),退出調(diào)度。如果本線程的通行證使用完畢,則應(yīng)該將它歸還給 仲裁者,重新喚醒等待線程,參與調(diào)度,競爭此通行證。
比如,下列偽碼中,threadFuncA和threadFuncB就需要申請同一張通行證:
例一:
int globalCounter = 0;
void threadFuncA (通行證類型
* 通行證)
{
獲取通行證;
globalCounter++;
歸還通行證;
}
Void threadFuncB (通行證類型
* 通行證)
{
獲取通行證;
globalCounter *= 2;
歸還通行證;
}
又比如,下列偽碼中,需要為ResourceClass類的對象引用計數(shù)器制定一張通行證
。
例二:
class ResourceClass
{
public:
resource & reference()
{
獲取通行證;
++refcounter;
printf(“當(dāng)前對象被引用了%u次”, refCounter);
釋放通行證;
}
private:
通行證類型
通行證;
unsigned int refCounter;
};
ResourceClass rescObj
Void threadFuncA()
{
rescObj-> reference();
}
Void threadFuncB()
{
rescObj-> reference();
}
最后一個例子,是為ResourceClass類的對象計數(shù)器制定一張通行證。
例三:
class ResourceClass
{
public:
ResourceClass ()
{
獲取通行證;
++objcounter;
printf(“當(dāng)前類創(chuàng)建了%u個對象”, objCounter);
釋放通行證;
}
private:
static通行證類型
通行證;
unsigned int objCounter;
};
Void threadFuncA()
{
ResourceClass * rescObj = new ResourceClass ();
}
Void threadFuncB()
{
ResourceClass * rescObj = new ResourceClass ();
}
這三個例子中,例一是不同函數(shù)之間互斥,所以通行證的作用域要對兩個函數(shù)都可見。例二是同一個對象的內(nèi)部函數(shù)多次調(diào)用之間的互斥,所以只要保證該函數(shù)多次 調(diào)用時共用的都是當(dāng)前對象內(nèi)部同一份通行證即可。例三是同一個類的所有對象在創(chuàng)建時都要互斥,所以必需保證這個類的所有對象構(gòu)造時共用的時同一份通行證, 從而通行證被聲明為靜態(tài)成員。
這里所說的“通行證”在多線程編程中對應(yīng)一個專門的術(shù)語mutex,由“mutual exclusion”拼接而來。為什么不直接用“鎖”的概念呢?因為“鎖”并不能很好的表達(dá)互斥的含義。鎖是指一定條件下不允許當(dāng)前代碼段執(zhí)行的概念。如 上述例二或例三,不允許多個線程同時執(zhí)行同一函數(shù),這時說這個函數(shù)被鎖定是很形象。但在例一中,A函數(shù)被鎖定,為什么B函數(shù)不能執(zhí)行呢?這就較難理解了。
而且經(jīng)常有人感到疑惑,為什么“加鎖后,被鎖定的關(guān)鍵域只能串行執(zhí)行”。這個其實是指在各自的時間軸上,并行的控制流在經(jīng)過互斥執(zhí)行的代碼段時,必需以先 后順序串行執(zhí)行。在今后的介紹中,mutex的申請,用acquire()操作表示,mutex的歸還,用release()表示。舍棄lock(), unlock()的表示。
為了深入理解,先來看一段使用忙等待實現(xiàn)互斥的代碼,用的是系統(tǒng)內(nèi)核中使用較多的“spin
lock”互斥方法。
例4.忙等待實現(xiàn)互斥
//聲明為volatile,防止被編譯器優(yōu)化。
volatile bool dataProcessNotDone = true;
int criticalData = 0;
unsigned threadFuncA( void* para )
{
//如果編譯器不支持volatile關(guān)鍵字,
//打開優(yōu)化選項時,此句可能直接變成死循環(huán)。
while (dataProcessNotDone); // spin lock,鎖定的是后續(xù)數(shù)據(jù)
//被鎖定的代碼區(qū)
printf("critical data is %d\n", CriticalData);
return 0;
}
unsigned threadFuncB( void* para )
{
sleep(1000);
criticalData++;
dataProcessNotDone = false; //修改互斥變量
return 0;
}
在高級語言中,利用spin lock實現(xiàn)復(fù)雜互斥條件非常困難,單單處理競爭條件就令人望而生畏。Spin lock在每次等待解鎖的時間都很短時,具有無需線程切換,無需再調(diào)度等獨特優(yōu)勢。但是在絕大多數(shù)應(yīng)用中,由于互斥等待時間不確定(可能很長),多個線程等待spin lock解鎖的過程中,spinning的行為可能導(dǎo)致系統(tǒng)處于半癱瘓狀態(tài),會嚴(yán)重影響程序性能。
除了忙等待之外,很多操作系統(tǒng)或線程庫都提供了互斥原語來實現(xiàn)互斥。如果有可能,應(yīng)當(dāng)盡量使用系統(tǒng)提供的接口實現(xiàn)互斥。否則,要考慮編譯器優(yōu)化中的常量代入,語句執(zhí)行順序重排,cpu指令序列優(yōu)化等依賴于具體軟硬件環(huán)境的復(fù)雜問題(關(guān)鍵是這樣的付出沒有太大意義)。
下面根據(jù)上述概念,抽象出互斥類的概念,定義如下Mutex類接口
5. 互斥類接口定義
文件mutex.h
#ifndef __MUTEX_H__
#define __MUTEX_H__
// C++標(biāo)準(zhǔn)中以_或__開頭的名字不符合標(biāo)準(zhǔn),
// 這一特點可以讓這樣定義的宏不會錯誤覆蓋其他名字。
class Mutex
{
public:
Mutex();
~Mutex();
bool acquire (bool block = true);
void release();
private:
//依賴于具體實現(xiàn),后面再說。
};
#endif
其中,
Mutex::acquire(),獲取互斥量,有阻塞和非阻塞兩種調(diào)用方式,阻塞方式,獲取互斥量失敗時線程休眠。非阻塞方式下,獲取失敗時直接返回。
Mutex::release(),釋放互斥量。
6.
示例程序
下面的例子說明了如何實現(xiàn)多線程環(huán)境下的Singleton模式。
文件Singleton.h:
#ifndef __SINGLETON_H__
#define __SINGLETON_H__
#include <stdio.h>
#include "thread.h"
#include "mutex.h"
// Dummy class.
class Helper {};
// A wrapper class for Mutex class
// It is exception safe.
class Guard
{
public:
Guard(Mutex & lock):mutex(lock)
{
mutex.acquire();
}
~Guard()
{
mutex.release();
}
private:
Mutex & mutex;
};
// Correct but possibly expensive multithreaded version
class Singleton1
{
public:
Helper * getInstance() {
Guard guard(mutex);
if (helper == NULL)
{
helper = new Helper();
}
return helper;
}
private:
static Mutex mutex;
static Helper * helper;
};
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Singleton2
{
public:
Helper * getInstance() {
if (helper == NULL)
{
Guard guard(mutex);
if (helper == NULL)
{
helper = new Helper();
}
}
return helper;
}
private:
static Mutex mutex;
static Helper * helper;
};
//Thread class for test.
template <typename T>
class TestThread: public Thread
{
public:
TestThread<typename T>(T & resource, Helper *&
res, Helper *&
res2Cmp)
:singleton(resource), instance(res), instance2Cmp(res2Cmp)
{}
protected:
void * run (void *)
{
for (int i=0; i<100000; i++)
{
instance = singleton.getInstance();
if (instance != instance2Cmp
&& instance != NULL
&&instance2Cmp != NULL
)
{
printf("Fail! %p <> %p.\n",
instance, instance2Cmp);
}
}
return NULL;
}
private:
T & singleton;
Helper * & instance;
Helper * & instance2Cmp;
};
#endif
文件main.cpp
#include <stdio.h>
#include "singleton.h"
#define SINGLETON Singleton1
Mutex SINGLETON::mutex;
Helper * SINGLETON::helper = NULL;
int main(int argc, char** argv)
{
Helper * instance1= NULL;
Helper * instance2 = NULL;
SINGLETON singleton;
TestThread<SINGLETON> thread1(singleton, instance1,
instance2);
TestThread<SINGLETON> thread2(singleton, instance2,
instance1);
thread1.start();
thread2.start();
thread1.wait();
thread2.wait();
printf("Finished!\n");
return 0;
}
對此示例程序,說明如下幾點。
1.定義了一個新的Guard類,這樣做的好處是做到異常安全。比如:
try
{
Mutex mutex;
mutex.acquire();
// Some operations
if (errorHappened)
throw Exception();
mutex.release();
}
catch (Exception & e)
{
// Print error message;
}
這段代碼中,拋出異常時,互斥量不能釋放,容易造成死鎖。使用Guard類重寫,
可以實現(xiàn)異常安全:
try
{
Mutex mutex;
Guard guard(mutex);
// Some operations
if (errorHappened)
throw Exception();
}
catch (Exception & e)
{
// Print error message;
}
2.Singleton1的實現(xiàn)可以確保多線程安全。但它是一個低效實現(xiàn)。假如有100次訪問,只有1次會修改instance指針,其余99次都是只讀操作。但是每次訪問都需要進(jìn)行互斥量的獲取和釋放操作。
取決于系統(tǒng)實現(xiàn)方式,互斥操作可能比整型變量的++操作慢一個數(shù)量級。有的實現(xiàn)提供的mutex其實是進(jìn)程級別的互斥,一次互斥操作,會進(jìn)入內(nèi)核態(tài),然后再返回用戶態(tài)。而有的線程庫提供的是Process local mutex,要稍快一些。但無論那種實現(xiàn),代價都較大。
因此,為了改進(jìn)Singleton1的實現(xiàn)效率,"Double-Checked Locking" idiom被提了出來。其思路是如果instance指針為空再進(jìn)入互斥操作。由于獲取互斥量過程中,可能別的線程已經(jīng)將instance指針賦值,所以需要在獲得互斥量所有權(quán)之后,再次檢查instance指針值。這就是所謂”double
check”中double的來歷。
Double check的設(shè)計很聰明,但可惜無論在C++中還是在Java中,這么做其實都不能保證線程安全??紤]如下序列:
Step1. 線程A檢查instance指針,發(fā)現(xiàn)其為空。獲取互斥量成功,運行至語句helper = new Helper();
在打開優(yōu)化選項時,這個語句在優(yōu)化后可能變成2步子操作, 而且編譯器自動調(diào)整了原語句的執(zhí)行順序(reordering):
1) 分配內(nèi)存,將地址賦值給helper變量。此時helper值非空。
2) 開始初始化此內(nèi)存。
在運行完子語句1后,線程發(fā)生切換,此時內(nèi)存尚未初始化。
Step2. 線程B檢查instance指針,發(fā)現(xiàn)其非空。對instance所指對象進(jìn)行進(jìn)一步操作,由于此時對象是初始化還未完成無效數(shù)據(jù),程序崩潰。
那么如何實現(xiàn)安全的double check呢?vc2005以后的版本,以及java
1.6以后的版本中,可以通過為helper加上volatile限定符,防止編譯優(yōu)化時調(diào)整指令執(zhí)行順序。最新的g++對volatile如何處理,沒 有查到相關(guān)資料。不過在C++中,只要本地匯編支持memoryBarrier指令,也可以通過在C++代碼中內(nèi)嵌匯編指令實現(xiàn)線程安全。
在此不再詳細(xì)討論。
除此之外,instance類型是否是基本類型,是否多核環(huán)境,都對不安全的double check版本運行結(jié)果有微妙影響。
3.無論編譯器如何實現(xiàn),無論硬件環(huán)境如何,即使它最慢,也應(yīng)該盡量使用系統(tǒng)提供的互斥原語。只有它是能夠確保安全的。通過系統(tǒng)接口實現(xiàn)互斥,可以避免考慮編譯優(yōu)化等復(fù)雜情況。一種觀點說volatile可以確保上述double
check有效,但是intel有技術(shù)人員專門從硬件的角度批駁了這個說法,他告訴大家,即使編譯器不做這個reordering, 處理器也可能在指令級別做。唯一能確保安全的,還是由系統(tǒng)實現(xiàn)的互斥接口。(照此說法,MS 和
Intel結(jié)成wintel聯(lián)盟還是必要的,呵呵)雙方說法似乎都一樣權(quán)威時,作為程序開發(fā)者,很難取舍,因此在此類問題上還是應(yīng)該適當(dāng)保守。
4.Mutex, volatile變量,普通變量在使用中的具體效率對比,是一個非常復(fù)雜的問題。涉及到內(nèi)存,緩存,寄存器之間各種情況的同步問題。不過大家可以針對一些簡單的例子,測試一下執(zhí)行時間上的差異。
7. Unix實現(xiàn)
下面是借助pthread的Mutex類實現(xiàn)。
文件Mutex.h
#ifndef __MUTEX_H__
#define __MUTEX_H__
#include <pthread.h>
class Mutex
{
public:
Mutex();
virtual ~Mutex();
virtual bool acquire (bool block = true);
virtual void release();
private:
pthread_mutex_t handle;
};
#endif
文件 Mutex.cpp
#include "mutex.h"
Mutex::Mutex()
{
pthread_mutex_init(&handle, NULL);
}
Mutex::~Mutex()
{
pthread_mutex_destroy(&handle);
}
bool Mutex::acquire(bool block)
{
if (block)
{
return pthread_mutex_lock(&handle) == 0;
}
else
{
return pthread_mutex_trylock(&handle) == 0;
}
}
void Mutex::release()
{
ReleaseMutex(handle);
}
Windows實現(xiàn)
文件mutex.h
#ifndef __MUTEX_H__
#define __MUTEX_H__
#include <windows.h>
class Mutex
{
public:
Mutex();
virtual ~Mutex();
virtual bool acquire (bool block = true);
virtual void release();
private:
HANDLE handle;
};
#endif
文件mutex.cpp
#include "mutex.h"
Mutex::Mutex()
{
handle = CreateMutex(NULL, false, NULL);
}
Mutex::~Mutex()
{
CloseHandle(handle);
}
bool Mutex::acquire(bool block)
{
//Use caution when calling the wait functions
//and code that directly or indirectly creates windows.
return WaitForSingleObject(handle, block ? INFINITE : 0) ==
WAIT_OBJECT_0;
}
void Mutex::release()
{
ReleaseMutex(handle);
}
小結(jié)
本節(jié)從控制流的角度進(jìn)一步介紹了什么是多線程執(zhí)行中的并發(fā),在此基礎(chǔ)上介紹了主動對象的概念。在多線程編程中,需要考慮線程協(xié)作的地方,如果執(zhí)行順序理不清,為每一個線程畫一條時間軸,標(biāo)出各自時序,對分析問題往往能有幫助。
本節(jié)也介紹了多線程設(shè)計的一個基本思想,就是主動對象要具有一定的獨立性,和其他線程的交互盡量只通過進(jìn)程環(huán)境、系統(tǒng)或線程庫提供的同步原語實現(xiàn)。
為什么要互斥,這個基本問題不能透徹理解的話,鎖的概念很容易把自己弄糊涂?;コ獾牧6却笮∫簿透緹o法談起。
互斥的效率問題,在實際設(shè)計中是一個值得考慮的問題。但是程序的正確性問題是一個更重要的問題。不能保證正確性,就不要用。保證正確性到實現(xiàn)高效性的過程,類似于學(xué)會走路到能夠飛奔的過程。對于初學(xué)者來說,欲速則不達(dá)。
為了對互斥量的使用,“通行證”所有權(quán)的轉(zhuǎn)換,以及不同系統(tǒng)中Mutex的實現(xiàn)效率等有一個充分的感性認(rèn)識,大家請多動手實現(xiàn)才能真正有所收益,最終超越我本人的一己知見。
最后,歡迎提出各種意見,以便這個系列能夠錯誤少一些,解釋準(zhǔn)確一些,幫助也更大一些。