多線程之線程同步Mutex (功能與CriticalSection相同,保證某一時刻只有一個線程能夠訪問共享資源,但是是內核對象,所以訪問速度要比CriticalSection要慢,但是增加了等待超時的功能,使用時可以根據實際的情況選擇其一)
一 Mutex
互斥對象(mutex)內核對象能夠確保線程擁有對單個資源的互斥訪問權。實際上互斥對象是因此而得名的。互斥對象包含一個使用數量,一個線程ID和一個遞歸計數器。
互斥對象的行為特性與關鍵代碼段相同,但是互斥對象屬于內核對象,而關鍵代碼段則屬于用戶方式對象。這意味著互斥對象的運行速度比關鍵代碼段要慢。但是這也意味著不同進程中的多個線程能夠訪問單個互斥對象,并且這意味著線程在等待訪問資源時可以設定一個超時值。
ID用于標識系統中的哪個線程當前擁有互斥對象,遞歸計數器用于指明該線程擁有互斥對象的次數。
互斥對象有許多用途,屬于最常用的內核對象之一。通常來說,它們用于保護由多個線程訪問的內存塊。如果多個線程要同時訪問內存塊,內存塊中的數據就可能遭到破壞。互斥對象能夠保證訪問內存塊的任何線程擁有對該內存塊的獨占訪問權,這樣就能夠保證數據的完整性。
互斥對象的使用規則如下:
• 如果線程ID是0(這是個無效ID),互斥對象不被任何線程所擁有,并且發出該互斥對象的通知信號。
• 如果ID是個非0數字,那么一個線程就擁有互斥對象,并且不發出該互斥對象的通知信號。
• 與所有其他內核對象不同, 互斥對象在操作系統中擁有特殊的代碼,允許它們違反正常的規則。
若要使用互斥對象,必須有一個進程首先調用CreateMutex,以便創建互斥對象:
HANDLECreateMutex(
PSECURITY_ATTRIBUTES psa,
BOOL fInitialOwner,
PCTSTR pszName);
InitialOwner參數用于控制互斥對象的初始狀態。如果傳遞FALSE(這是通常情況下傳遞的值),那么互斥對象的ID和遞歸計數器均被設置為0。這意味著該互斥對象沒有被任何線程所擁有,因此要發出它的通知信號。
如果為fInitialOwner參數傳遞TRUE,那么該對象的線程ID被設置為調用線程的ID,遞歸計數器被設置為1。由于ID是個非0數字,因此該互斥對象開始時不發出通知信號。
通過調用一個等待函數,并傳遞負責保護資源的互斥對象的句柄,線程就能夠獲得對共享資源的訪問權。在內部,等待函數要檢查線程的ID,以了解它是否是0(互斥對象發出通知信號)。如果線程ID是0,那么該線程ID被設置為調用線程的ID,遞歸計數器被設置為1,同時,調用線程保持可調度狀態。
如果等待函數發現ID不是0(不發出互斥對象的通知信號),那么調用線程便進入等待狀態。系統將記住這個情況,并且在互斥對象的ID重新設置為0時,將線程ID設置為等待線程的ID,將遞歸計數器設置為1,并且允許等待線程再次成為可調度線程。與所有情況一樣,對互斥內核對象進行的檢查和修改都是以原子操作方式進行的。
一旦線程成功地等待到一個互斥對象,該線程就知道它已經擁有對受保護資源的獨占訪問權。試圖訪問該資源的任何其他線程(通過等待相同的互斥對象)均被置于等待狀態中。當目前擁有對資源的訪問權的線程不再需要它的訪問權時,它必須調用ReleaseMutex函數來釋放該互斥對象:
BOOL ReleaseMutex(HANDLE hMutex);
該函數將對象的遞歸計數器遞減1。
當該對象變為已通知狀態時,系統要查看是否有任何線程正在等待互斥對象。如果有,系統將“按公平原則”選定等待線程中的一個,為它賦予互斥對象的所有權。當然,這意味著線程I D被設置為選定的線程的ID,并且遞歸計數器被置為1。如果沒有其他線程正在等待互斥對象,那么該互斥對象將保持已通知狀態,這樣,等待互斥對象的下一個線程就立即可以得到互斥對象。
二 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. |
三 實例
來自msdn的實例:在線程函數中有一個循環,在每個循環的開始都取得Mutex,然后對全局或靜態操作,相當于在關鍵代碼段操作,然后在使用完以后釋放它,大家可以執行,查看結果。
#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;
}
四 參考