測(cè)試環(huán)境Windows XP SP2,,Intel Core2 Duo E8400 3.00GHz,1.94GB內(nèi)存
首先使用CRITICAL_SECTION和自旋機(jī)制實(shí)現(xiàn):
CRITICAL_SECTION GCritical;
......
InitializeCriticalSectionAndSpinCount(&GCritical, 0x80000FA0);
......
// 線程核心函數(shù),共使用兩個(gè)線程
for (int i = 0; i < 100000000; ++i)
{
EnterCriticalSection(&GCritical);
++GlobalSum;
LeaveCriticalSection(&GCritical);
}
......
DeleteCriticalSection(&GCritical);
實(shí)測(cè)時(shí)間為21.578s
使用CRITICAL_SECTION和非自旋機(jī)制:
CRITICAL_SECTION GCritical;
......
InitializeCriticalSection(&GCritical);
......
// 線程核心函數(shù),共使用兩個(gè)線程
for (int i = 0; i < 100000000; ++i)
{
EnterCriticalSection(&GCritical);
++GlobalSum;
LeaveCriticalSection(&GCritical);
}
......
DeleteCriticalSection(&GCritical);
運(yùn)行時(shí)間太長(zhǎng),在運(yùn)行2分鐘后仍然未運(yùn)行完畢
可見(jiàn)在大規(guī)模并行運(yùn)算的環(huán)境中,如果存在臨界區(qū),自旋鎖的時(shí)間開(kāi)銷遠(yuǎn)小于普通的鎖機(jī)制。相比于普通鎖,自旋鎖在進(jìn)入操
作系統(tǒng)內(nèi)核態(tài)之前會(huì)進(jìn)行多次循環(huán),并在循環(huán)中嘗試獲取鎖,如果在循環(huán)過(guò)程中自旋鎖獲得了鎖則自旋鎖所在線
程不必由用戶態(tài)陷入內(nèi)核態(tài),也不必交出自己的時(shí)間片進(jìn)入等待狀態(tài)(見(jiàn)操作系統(tǒng)原理)。從而大大減少的獲取鎖的時(shí)間
有沒(méi)有比自旋鎖開(kāi)銷更小的鎖機(jī)制呢?答案是有,下面就是一個(gè)利用X86 Interlock系列指令實(shí)現(xiàn)的win32用戶態(tài)鎖:
class CLocker
{
private:
_declspec(align(4)) volatile long m_lThreadId;
volatile long m_lLockCount;
int m_iCollision;
public:
CLocker()
:m_lThreadId(0)
,m_lLockCount(0)
,m_iCollision(0)
{}
~CLocker()
{
if (m_lLockCount > 0)
__debugbreak();
}
void Lock()
{
const long tId = ::GetCurrentThreadId();
if (tId == m_lThreadId)
{
++m_lLockCount;
}
else
{
int iCol = 0;
while (true)
{
++iCol;
if (_InterlockedCompareExchange(&m_lThreadId, tId, 0) == 0)
{
m_lLockCount = 1;
if (iCol > m_iCollision)
m_iCollision = iCol;
return;
}
//volatile int delay;
//const int maxDelay = (int)((rand() / (float)RAND_MAX) * 100) * 50;
Sleep(0); //for (delay = 0; delay < maxDelay; ++delay); // 注1
}
}
void Release()
{
const long tId = ::GetCurrentThreadId();
if (tId == m_lThreadId)
{
if (--m_lLockCount == 0)
{
//_InterlockedExchange(&m_lThreadId, 0);
m_lThreadId = 0; // 這里不用Interlock操作,機(jī)器字對(duì)齊時(shí)寫(xiě)入為原子操作
}
}
int GetCollision()
{
return m_iCollision;
}
};
該類通過(guò)Interlock系列指令集所具有的原子操作特性互斥的將臨界區(qū)資源交給獲得鎖的線程。所有操作均在用戶態(tài)執(zhí)行,邏輯簡(jiǎn)單,最終編譯結(jié)果指令數(shù)也非常少。極大提高了加鎖效率。注1處的循環(huán)是一個(gè)關(guān)鍵點(diǎn)。_InterlockedCompareExchange操作如果失敗會(huì)導(dǎo)致CPU執(zhí)行復(fù)雜的Cache操作,從而浪費(fèi)大量時(shí)間。使CAS失敗的線程等待,減少多線程爭(zhēng)用同一鎖時(shí)產(chǎn)生過(guò)多的_InterlockedCompareExchange操作失敗,從而提高加鎖效率。以下是測(cè)試結(jié)果
static CLocker GLocker;
......
// 線程核心函數(shù),共兩個(gè)線程
for (int i = 0; i < 100000000; ++i)
{
GLocker.Lock();
++GlobalSum;
GLocker.Release();
}
......
測(cè)試結(jié)果為5.969s,可見(jiàn)用戶態(tài)鎖確實(shí)具有相對(duì)較高的效率。
需要說(shuō)明的是,我的本意只是為了探討不同種類鎖的效率,實(shí)際情況中,多線程編程要盡量減少多線程間的資源爭(zhēng)用,從設(shè)計(jì)上避免頻繁加鎖解鎖(如Lock-Free算法)。
補(bǔ)充說(shuō)明,windows平臺(tái)和xbox360平臺(tái)上的CriticalSection實(shí)際上包含一個(gè)內(nèi)存屏障,保證臨界區(qū)前后的讀寫(xiě)操作不會(huì)越界執(zhí)行(見(jiàn)編譯器對(duì)指令的優(yōu)化和CPU的指令亂序執(zhí)行),一個(gè)InterlockedXxx操作,嵌套檢查,滿足特定條件向Mutex操作退化。CriticalSection使用合適的自旋次數(shù)能提高效率。CLocker由于沒(méi)有內(nèi)存屏障實(shí)際上在某些情況下會(huì)不如CriticalSection安全,使用CLocker一定需要程序員自己保證內(nèi)存操作的順序性。
實(shí)際上臨界區(qū)爭(zhēng)用導(dǎo)致線程等待的時(shí)間往往大于鎖執(zhí)行的時(shí)間,優(yōu)化加鎖解鎖速度實(shí)際上并未解決根本問(wèn)題。