【多線程】C++ 互斥鎖(mutex)的簡單原理分析


多線程是多任務處理的一種特殊形式,多任務處理允許讓電腦同時運行兩個或兩個以上的程序。一般情況下,分為兩種類型的多任務處理:基於進程和基於線程

  1)基於進程的多任務處理是程序的並發執行。

  2)基於線程的多任務處理是同一程序的片段的並發執行。

多線程程序包含可以同時運行的兩個或多個部分。這樣的程序中的每個部分稱為一個線程,每個線程定義了一個單獨的執行路徑。在多任務操作系統中,同時運行的多個任務可能都需要使用同一種資源。比如說,同一個文件,可能一個線程會對其進行寫操作,而另一個線程需要對這個文件進行讀操作,可想而知,如果寫線程還沒有寫結束,而此時讀線程開始了,或者讀線程還沒有讀結束而寫線程開始了,那么最終的結果顯然會是混亂的。為了保護共享資源,在線程里也有這么一把鎖——互斥鎖(mutex),互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態,即上鎖( lock )和解鎖( unlock )。

一、創建線程

在Windows下用C++創建線程需要導入windows.h頭文件,同時調用CreateThread()函數。如下:

#include <windows.h>
HANDLE thread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);

參數說明:

  1)In_opt LPSECURITY_ATTRIBUTES:lpThreadAttributes, {安全設置}

  指向 TSecurityAttributes 結構的指針,一般都是置為NULL,這表示沒有訪問限制。

  2)In SIZE_T:dwStackSize, {堆棧大小}

  分配給線程的堆棧大小,每個線程都有自己獨立的堆棧(也擁有自己的消息隊列)。它們都是進程中的內存區域,主要是存取方式不同(棧:先進后出;堆:先進先出)。

  3)In LPTHREAD_START_ROUTINE:lpStartAddress, {入口函數}

  線程入口函數的參數是個無類型指針,用它可以指定任何數據。

  4)In_opt __drv_aliasesMem LPVOID:lpParameter, {函數參數}

  函數指針,新線程建立后將立即執行該函數,函數執行完畢,系統將銷毀此線程從而結束多線程的程序。

  5)In DWORD:dwCreationFlags, {啟動選項}

  啟動選項)有兩個可選值:0(線程建立后立即執行入口函數);CREATE_SUSPENDED(線程建立后會掛起等待)。

  6)Out_opt LPDWORD:lpThreadId {輸出線程id}

  輸出線程句柄ID,注意: 1、線程的 ID 是唯一的;而句柄可能不只一個,比如可以用 GetCurrentThread 獲取一個偽句柄、可以用 DuplicateHandle 復制一個句柄等。2、ID 比句柄更輕便,在主線程中 GetCurrentThreadId、MainThreadID獲取的都是主線程的 ID。

返回值:線程句柄 ,"句柄" 類似指針,但通過指針可讀寫對象,通過句柄只是使用對象;有句柄的對象一般都是系統級別的對象(或叫內核對象)。

二、創建互斥量

互斥量和二元信號量類似,資源僅允許一個線程訪問。與二元信號量不同的是,信號量在整個系統中可以被任意線程獲取和釋放,也就是說,同一個信號量可以由一個線程獲取而由另一線程釋放。而互斥量則要求哪個線程獲取了該互斥量鎖就由哪個線程釋放,其它線程越俎代庖釋放互斥量是無效的。

在使用互斥量進行線程同步時會用到以下幾個函數:

HANDLE WINAPI CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,        //線程安全相關的屬性,常置為NULL
    BOOL                  bInitialOwner,            //創建Mutex時的當前線程是否擁有Mutex的所有權
    LPCTSTR               lpName                    //Mutex的名稱
);

其中,lpMutexAttributes也是表示安全的結構,與CreateThread中的lpThreadAttributes功能相同,表示決定返回的句柄是否可被子進程繼承,如果為NULL則表示返回的句柄不能被子進程繼承。bInitialOwner表示創建Mutex時的當前線程是否擁有Mutex的所有權,若為TRUE則指定為當前的創建線程為Mutex對象的所有者,其它線程訪問需要先ReleaseMutex。lpName為Mutex的名稱。

DWORD WINAPI WaitForSingleObject(
    HANDLE hHandle,                                 //要獲取的鎖的句柄
    DWORD  dwMilliseconds                           //超時間隔
);

其中,WaitForSingleObject的作用是等待一個指定的對象(如Mutex對象),直到該對象處於非占用的狀態(如Mutex對象被釋放)或超出設定的時間間隔。除此之外,還有一個與它類似的函數WaitForMultipleObjects,它的作用是等待一個或所有指定的對象,直到所有的對象處於非占用的狀態,或超出設定的時間間隔。

BOOL WINAPI ReleaseMutex(HANDLE hMutex);

Handle:要等待的指定對象的句柄。dwMilliseconds:超時的間隔,以毫秒為單位;如果dwMilliseconds為非0,則等待直到dwMilliseconds時間間隔用完或對象變為非占用的狀態,如果dwMilliseconds 為INFINITE則表示無限等待,直到等待的對象處於非占用的狀態。釋放所擁有的互斥量鎖對象,hMutex為釋放的互斥量的句柄。

int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr , int *restrict type);   //獲取互斥鎖類型
int pthread_mutexattr_settype(const pthread_mutexattr_t *restrict attr , int type);             //設置互斥鎖類型

參數type表示互斥鎖的類型,總共有以下四種類型:
  1)PTHREAD_MUTEX_NOMAL:標准互斥鎖,第一次上鎖成功,第二次上鎖會失敗並阻塞;
  2)PTHREAD_MUTEX_RECURSIVE:遞歸互斥鎖,第一次上鎖成功,第二次上鎖還是會成功,可以理解為內部有一個計數器,每加一次鎖計數器加1,解鎖減1;
  3)PTHREAD_MUTEX_ERRORCHECK:檢查互斥鎖,第一次上鎖會成功,第二次上鎖出錯返回錯誤信息,不會阻塞;
  4)PTHREAD_MUTEX_DEFAULT:默認互斥鎖,第一次上鎖會成功,第二次上鎖會失敗。

三、采用互斥鎖實現線程同步

#include <windows.h>
#include <iostream>
 
#define NAME_LINE   40
 
//定義線程函數傳入參數的結構體
typedef struct __THREAD_DATA
{
    int nMaxNum;
    char strThreadName[NAME_LINE];
 
    __THREAD_DATA() : nMaxNum(0)
    {
        memset(strThreadName, 0, NAME_LINE * sizeof(char));
    }
}THREAD_DATA;
 
HANDLE g_hMutex = NULL;     //互斥量
 
//線程函數
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    THREAD_DATA* pThreadData = (THREAD_DATA*)lpParameter;
 
    for (int i = 0; i < pThreadData->nMaxNum; ++ i)
    {
        //請求獲得一個互斥量鎖
        WaitForSingleObject(g_hMutex, INFINITE);
        cout << pThreadData->strThreadName << " --- " << i << endl;
        Sleep(100);
        //釋放互斥量鎖
        ReleaseMutex(g_hMutex);
    }
    return 0L;
}
 
int main()
{
    //創建一個互斥量
    g_hMutex = CreateMutex(NULL, FALSE, NULL);
 
    //初始化線程數據
    THREAD_DATA threadData1, threadData2;
    threadData1.nMaxNum = 5;
    strcpy_s(threadData1.strThreadName, "線程1");
    threadData2.nMaxNum = 10;
    strcpy_s(threadData2.strThreadName, "線程2");
 
 
    //創建第一個子線程
    HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc, &threadData1, 0, NULL);
    //創建第二個子線程
    HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc, &threadData2, 0, NULL);
    //關閉線程
    CloseHandle(hThread1);
    CloseHandle(hThread2);
 
    //主線程的執行路徑
    for (int i = 0; i < 5; ++ i)
    {
        //請求獲得一個互斥量鎖
        WaitForSingleObject(g_hMutex, INFINITE);
        cout << "主線程 === " << i << endl;
        Sleep(100);
        //釋放互斥量鎖
        ReleaseMutex(g_hMutex);
    }
 
    system("pause");
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM