多線程之線程同步Mutex (功能與CriticalSection相同,保證某一時(shí)刻只有一個(gè)線程能夠訪問共享資源,但是是內(nèi)核對(duì)象,所以訪問速度要比CriticalSection要慢,但是增加了等待超時(shí)的功能,使用時(shí)可以根據(jù)實(shí)際的情況選擇其一)
一 Mutex
互斥對(duì)象(mutex)內(nèi)核對(duì)象能夠確保線程擁有對(duì)單個(gè)資源的互斥訪問權(quán)。實(shí)際上互斥對(duì)象是因此而得名的。互斥對(duì)象包含一個(gè)使用數(shù)量,一個(gè)線程ID和一個(gè)遞歸計(jì)數(shù)器。
互斥對(duì)象的行為特性與關(guān)鍵代碼段相同,但是互斥對(duì)象屬于內(nèi)核對(duì)象,而關(guān)鍵代碼段則屬于用戶方式對(duì)象。這意味著互斥對(duì)象的運(yùn)行速度比關(guān)鍵代碼段要慢。但是這也意味著不同進(jìn)程中的多個(gè)線程能夠訪問單個(gè)互斥對(duì)象,并且這意味著線程在等待訪問資源時(shí)可以設(shè)定一個(gè)超時(shí)值。
ID用于標(biāo)識(shí)系統(tǒng)中的哪個(gè)線程當(dāng)前擁有互斥對(duì)象,遞歸計(jì)數(shù)器用于指明該線程擁有互斥對(duì)象的次數(shù)。
互斥對(duì)象有許多用途,屬于最常用的內(nèi)核對(duì)象之一。通常來說,它們用于保護(hù)由多個(gè)線程訪問的內(nèi)存塊。如果多個(gè)線程要同時(shí)訪問內(nèi)存塊,內(nèi)存塊中的數(shù)據(jù)就可能遭到破壞。互斥對(duì)象能夠保證訪問內(nèi)存塊的任何線程擁有對(duì)該內(nèi)存塊的獨(dú)占訪問權(quán),這樣就能夠保證數(shù)據(jù)的完整性。
互斥對(duì)象的使用規(guī)則如下:
• 如果線程ID是0(這是個(gè)無效ID),互斥對(duì)象不被任何線程所擁有,并且發(fā)出該互斥對(duì)象的通知信號(hào)。
• 如果ID是個(gè)非0數(shù)字,那么一個(gè)線程就擁有互斥對(duì)象,并且不發(fā)出該互斥對(duì)象的通知信號(hào)。
• 與所有其他內(nèi)核對(duì)象不同, 互斥對(duì)象在操作系統(tǒng)中擁有特殊的代碼,允許它們違反正常的規(guī)則。
若要使用互斥對(duì)象,必須有一個(gè)進(jìn)程首先調(diào)用CreateMutex,以便創(chuàng)建互斥對(duì)象:
HANDLECreateMutex(
PSECURITY_ATTRIBUTES psa,
BOOL fInitialOwner,
PCTSTR pszName);
InitialOwner參數(shù)用于控制互斥對(duì)象的初始狀態(tài)。如果傳遞FALSE(這是通常情況下傳遞的值),那么互斥對(duì)象的ID和遞歸計(jì)數(shù)器均被設(shè)置為0。這意味著該互斥對(duì)象沒有被任何線程所擁有,因此要發(fā)出它的通知信號(hào)。
如果為fInitialOwner參數(shù)傳遞TRUE,那么該對(duì)象的線程ID被設(shè)置為調(diào)用線程的ID,遞歸計(jì)數(shù)器被設(shè)置為1。由于ID是個(gè)非0數(shù)字,因此該互斥對(duì)象開始時(shí)不發(fā)出通知信號(hào)。
通過調(diào)用一個(gè)等待函數(shù),并傳遞負(fù)責(zé)保護(hù)資源的互斥對(duì)象的句柄,線程就能夠獲得對(duì)共享資源的訪問權(quán)。在內(nèi)部,等待函數(shù)要檢查線程的ID,以了解它是否是0(互斥對(duì)象發(fā)出通知信號(hào))。如果線程ID是0,那么該線程ID被設(shè)置為調(diào)用線程的ID,遞歸計(jì)數(shù)器被設(shè)置為1,同時(shí),調(diào)用線程保持可調(diào)度狀態(tài)。
如果等待函數(shù)發(fā)現(xiàn)ID不是0(不發(fā)出互斥對(duì)象的通知信號(hào)),那么調(diào)用線程便進(jìn)入等待狀態(tài)。系統(tǒng)將記住這個(gè)情況,并且在互斥對(duì)象的ID重新設(shè)置為0時(shí),將線程ID設(shè)置為等待線程的ID,將遞歸計(jì)數(shù)器設(shè)置為1,并且允許等待線程再次成為可調(diào)度線程。與所有情況一樣,對(duì)互斥內(nèi)核對(duì)象進(jìn)行的檢查和修改都是以原子操作方式進(jìn)行的。
一旦線程成功地等待到一個(gè)互斥對(duì)象,該線程就知道它已經(jīng)擁有對(duì)受保護(hù)資源的獨(dú)占訪問權(quán)。試圖訪問該資源的任何其他線程(通過等待相同的互斥對(duì)象)均被置于等待狀態(tài)中。當(dāng)目前擁有對(duì)資源的訪問權(quán)的線程不再需要它的訪問權(quán)時(shí),它必須調(diào)用ReleaseMutex函數(shù)來釋放該互斥對(duì)象:
BOOL ReleaseMutex(HANDLE hMutex);
該函數(shù)將對(duì)象的遞歸計(jì)數(shù)器遞減1。
當(dāng)該對(duì)象變?yōu)橐淹ㄖ獱顟B(tài)時(shí),系統(tǒng)要查看是否有任何線程正在等待互斥對(duì)象。如果有,系統(tǒng)將“按公平原則”選定等待線程中的一個(gè),為它賦予互斥對(duì)象的所有權(quán)。當(dāng)然,這意味著線程I D被設(shè)置為選定的線程的ID,并且遞歸計(jì)數(shù)器被置為1。如果沒有其他線程正在等待互斥對(duì)象,那么該互斥對(duì)象將保持已通知狀態(tài),這樣,等待互斥對(duì)象的下一個(gè)線程就立即可以得到互斥對(duì)象。
二 API
Mutex function |
Description |
CreateMutex |
Creates or opens a named or unnamed mutex object. |
CreateMutexEx |
Creates or opens a named or unnamed mutex object and returns a handle to the object. |
OpenMutex |
Opens an existing named mutex object. |
ReleaseMutex |
Releases ownership of the specified mutex object. |
三 實(shí)例
來自msdn的實(shí)例:在線程函數(shù)中有一個(gè)循環(huán),在每個(gè)循環(huán)的開始都取得Mutex,然后對(duì)全局或靜態(tài)操作,相當(dāng)于在關(guān)鍵代碼段操作,然后在使用完以后釋放它,大家可以執(zhí)行,查看結(jié)果。
#include <windows.h>
#include <stdio.h>

#define THREADCOUNT 64 //less than 64
HANDLE ghMutex;
int g_x = 0;

DWORD WINAPI WriteToDatabase(LPVOID);

void main()


{
HANDLE aThread[THREADCOUNT];
DWORD ThreadID;
int i;

// Create a mutex with no initial owner
ghMutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex

if (ghMutex == NULL)

{
printf("CreateMutex error: %d\n", GetLastError());
return;
}

// Create worker threads

for( i=0; i < THREADCOUNT; i++ )

{
aThread[i] = CreateThread(
NULL, // default security attributes
0, // default stack size
(LPTHREAD_START_ROUTINE) WriteToDatabase,
NULL, // no thread function arguments
0, // default creation flags
&ThreadID); // receive thread identifier

if( aThread[i] == NULL )

{
printf("CreateThread error: %d\n", GetLastError());
return;
}
}

// Wait for all threads to terminate

WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

// Close thread and mutex handles
for( i=0; i < THREADCOUNT; i++ )
CloseHandle(aThread[i]);
CloseHandle(ghMutex);

printf("g_x is :%d\n",g_x);
}

DWORD WINAPI WriteToDatabase( LPVOID lpParam )


{
DWORD dwCount=0, dwWaitResult;

// Request ownership of mutex.

while( dwCount < 100 )

{
dwWaitResult = WaitForSingleObject(
ghMutex, // handle to mutex
INFINITE); // no time-out interval
switch (dwWaitResult)

{
// The thread got ownership of the mutex
case WAIT_OBJECT_0:

__try
{
g_x++;
// TODO: Write to the database
printf("Thread %d writing to database
\n",
GetCurrentThreadId());
dwCount++;
}


__finally
{
// Release ownership of the mutex object
if (! ReleaseMutex(ghMutex))

{
// Deal with error.
}
}
break;

// The thread got ownership of an abandoned mutex
case WAIT_ABANDONED:
return FALSE;
}
}
return TRUE;
}
四 參考