天天看點

Windows 平台下的同步機制 (2)– 互斥體(Mutex)

windows api中提供了一個互斥體,功能上要比臨界區強大。Mutex是互斥體的意思,當一個線程持有一個Mutex時,其它線程申請持有同一個Mutex會被阻塞,是以可以通過Mutex來保證對某一資源的互斥通路(即同一時間最多隻有一個線程通路)。

調用CreateMutex可以建立或打開一個Mutex對象,其原型如下

HANDLE CreateMutex(

  LPSECURITY_ATTRIBUTES lpMutexAttributes,

  BOOL bInitialOwner,

  LPCTSTR lpName

);

其中參數lpMutexAttributes用來設定Mutex對象的安全描述符和是否允許子程序繼承句柄。bInitialOwner表明是否将Mutex的持有者設定為調用線程。lpName參數設定Mutex的名字,該名字區分大小寫并不能包含"",最大長度為MAX_PATH,可設定為NULL表明該Mutex為匿名對象。

如果調用成功,則傳回Mutex的句柄,否則傳回NULL,如果lpName不為NULL且調用前同名的Mutex已被建立,則傳回同名Mutex的句柄,此時調用GetLastError将傳回ERROR_ALREADY_EXISTS,參數bInitialOwner将被忽略。

還可以調用OpenMutex打開建立的非匿名Mutex,原型如下

HANDLE OpenMutex(

  DWORD dwDesiredAccess,

  BOOL bInheritHandle,

  LPCTSTR lpName

);

在成功建立或打開Mutex後,可以使用wait functions來等待并擷取Mutex的持有權。

下面的例子用來通過Mutex對象控制某一應用程式隻運作一次

    int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

    {

        HANDLE hMutex = CreateMutex(NULL, FALSE, "Mutex_Only_One_Instance_Allowed");

        if (NULL == hMutex)

        {

            Error("Create mutex error.");

            return -1;

        }

        DWORD dw = WaitForSingleObject(hMutex, 0);

        if (WAIT_FAILED == dw)

        {

            Error("Wait for mutex error.");

            CloseHandle(hMutex); // 釋放句柄,當指向同一系統對象的所有句柄釋放後,該對象将被删除。

            return -1;

        }

        else if (WAIT_TIMEOUT == dw)

        {

            // 另外一個執行個體正在運作

            CloseHandle(hMutex);

            return 1;

        }

        // 沒有其它執行個體在運作,本執行個體将繼續運作

        // 在此實作必要的功能性代碼,如建立視窗,進入消息循環

        // ……………

        ReleaseMutex(hMutex); // 釋放hMutex的持有權,注意這并不等同于删除Mutex對象

        CloseHandle(hMutex);

        return 0;

    }

其中WaitForSingleObject是等待特定對象發出信号(signaled),而Mutex對象在沒有任何線程持有時會發出信号。

與臨界區(critical section)有什麼差別,為什麼強大?它們有以下幾點不一緻:

1.critical section是局部對象,而mutex是核心對象。是以像waitforsingleobject是不可以等待臨界區的。

2.critical section是快速高效的,而mutex同其相比要慢很多

3.critical section使用範圍是單一程序中的各個線程,而mutex由于可以有一個名字,是以它是可以應用于不同的程序,當然也可以應用于同一個程序中的不同線程。

4.critical section 無法檢測到是否被某一個線程釋放,而mutex在某一個線程結束之後會産生一個abandoned的資訊。同時mutex隻能被擁有它的線程釋放。下面舉兩個應用mutex的例子,一個是程式隻能運作一個執行個體,也就是說同一個程式如果已經運作了,就不能再運作了;另一個是關于非常經典的哲學家吃飯問題的例子。

互斥體通常用于多程序之間的同步問題

程式運作單個執行個體:

#include "stdafx.h"

#include

#include

#include

using namespace std;

//當輸入s或者c時候結束程式

void PrintInfo(HANDLE& h, char t)

{

    char c;

    while (1)

    {

        cin >> c;

        if (c == t)

        {

            ReleaseMutex(h);

            CloseHandle(h);

            break;

        }

        Sleep(100);

    }

}

int main(int argc, char* argv[])

{

    //建立mutex,當已經程式發現已經有這個mutex時候,就相當于openmutex

    HANDLE hHandle = CreateMutex(NULL, FALSE, "mutex_test");

    if (GetLastError() == ERROR_ALREADY_EXISTS)

    {

        cout << "you had run this program!" << endl;

        cout << "input c to close this window" << endl;

        PrintInfo(hHandle, ‘c’);

        return 1;

    }

    cout << "program run!" << endl;

    cout << "input s to exit program" <

    PrintInfo(hHandle, ‘s’);

    return 1;

}

封裝:

struct _Lock

{

    _Lock(HANDLE& mtx) : _mtx(mtx), _ret(WAIT_OBJECT_0)

    {

        if (_mtx != 0)

            _ret = WaitForSingleObject(_mtx, 100000);

    }

    ~_Lock()

    {

        Release();

    }

    void Release()

    {

        if (_mtx != 0 && _ret == WAIT_OBJECT_0)

            ReleaseMutex(_mtx);

    }

    HANDLE& _mtx;

    DWORD _ret;

};

哲學家吃飯問題:

const int PHILOSOPHERS = 5;          //哲學家人數

const int TIME_EATING = 50;         //吃飯需要的時間 毫秒

HANDLE event[PHILOSOPHERS];    //主線程同工作線程保持同步的句柄數組

HANDLE mutex[PHILOSOPHERS];   //mutex數組,這裡相當于公共資源筷子

CRITICAL_SECTION cs;                //控制列印的臨界區變量

UINT WINAPI ThreadFunc(void* arg)

{

    int num = (int)arg;

    DWORD ret = 0;

    while (1)

    {

        ret = WaitForMultipleObjects(2, mutex, TRUE, 1000);

        if (ret == WAIT_TIMEOUT)

        {

            Sleep(100);

            continue;

        }

        EnterCriticalSection(&cs);

            cout << "philosopher " << num << " eatting" << endl;

        LeaveCriticalSection(&cs);

        Sleep(TIME_EATING);

        break;

    }

    //設定時間為有信号

    SetEvent(event[num]);

    return 1;

}

int main(int argc, char* argv[])

{

    HANDLE hThread;

    InitializeCriticalSection(&cs);

    //循環建立線程

    for (int i = 0; i < PHILOSOPHERS; i++)

    {

        mutex[i] = CreateMutex(NULL, FALSE, "");

        event[i] = CreateEvent(NULL, TRUE, FALSE, "");

        hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)i, 0, NULL);

        if (hThread == 0)

        {

            cout << "create thread " << i << "failed with code: "

                << GetLastError() << endl;

            DeleteCriticalSection(&cs);

            return -1;

        }

        CloseHandle(hThread);

    }

    //等待所有的哲學家吃飯結束

    DWORD ret = WaitForMultipleObjects(PHILOSOPHERS, event, TRUE, INFINITE);

    if (ret == WAIT_OBJECT_0)

    {

        cout << "all the philosophers had a dinner!" << endl;

    }

    else

    {

        cout << "WaitForMultipleObjects failed with code: " << GetLastError() << endl;

    }

    DeleteCriticalSection(&cs);

    for (int j = 0; j < PHILOSOPHERS; j++)

    {

        CloseHandle(mutex[j]);

    }

    return 1;

}

Further reading:

1.Windows 平台下的同步機制 (1)– 臨界區(CriticalSection)

2.Windows 平台下的同步機制 (2)– 互斥體(Mutex)

3.Windows 平台下的同步機制 (3)– 事件(Event)

4.Windows 平台下的同步機制 (4)– 信号量(Semaphore)

5.《windows核心程式設計》學習筆記(一)核心對象

Reference:

1.http://www.chinaitpower.com/A200507/2005-07-27/176735.html

2.http://www.cppblog.com/wangjt/archive/2008/01/30/42235.aspx

繼續閱讀