天天看點

C++基礎學習之16 - 多線程與互斥鎖

        熟悉Linux的童鞋都對多程序比較熟,Linux下的 fork也有很多教程介紹,但這不是我們本節的重點,我們在這裡主要講的是多線程。

        相對于程序來講,線程 是一個輕量級的概念,一個程序包含多個線程(至少1個)。

        線程有自己的堆棧和局部變量,但沒有記憶體空間,而是共享程序的記憶體空間。

        這種共享記憶體機制 優點在于通過資料共享的快速通路,同樣這種機制 也會導緻 一個線程死掉的時候可能導緻整個程式崩潰。

C++基礎學習之16 - 多線程與互斥鎖

        那麼多線程之間如何 有效共享一段資料呢?

一. 臨界區(Critical Section)

        臨界區 是Windows下專用的一種同步機制,一次隻允許一個線程使用的共享資源,如果已經有線程進入了臨界區,則其它線程必須等待。臨界區 類似一種原子操作。

void thread_func()  
{
    // 進入臨界區  
    EnterCriticalSection();  
    
    // do something

    // 離開臨界區
    LeaveCriticalSection();  
} 
           

        臨界區 通過代碼段的方式來控制 記憶體或資料的共享,其實作較為簡單。

二. 互斥鎖(Mutex)

        互斥鎖 與臨界區類似,也是確定同一時刻隻能由一個線程進行通路,其差別在于:

1. Mutex可以跨程序,臨界區隻能在同一程序中使用;

2. Mutex是核心對象(核心态操作),速度慢;臨界區是非核心對象(使用者态操作),速度快;

3. Mutex和臨界區在Windows平台都下可用,Linux下隻能用 Mutex。

        對比一下在Linux和Windows平台下的Mutex使用代碼:

/** Linux*/
#include <pthread.h>
pthread_mutex_t m_mutex;
pthread_mutex_init(&m_Mutex, NULL);

int nRet = pthread_mutex_lock(&m_Mutex);
…… // do something
nRet = pthread_mutex_unlock(&m_Mutex);

pthread_mutex_destroy(&m_Mutex); // 銷毀

/** Windows*/
#include "windows.h"
HANDLE m_Mutex = CreateMutex(NULL, FALSE, NULL);
if( WAIT_OBJECT_0 == WaitForSingleObject(m_Mutex, INFINITE) )
{
    …… // do something
    ReleaseMutex(m_Mutex);
}
CloseHandle(m_Mutex); // 銷毀
           

三. 信号量(Semaphore)

        信号量 是Mutex的一種擴充形式,信号量可以看作是一個資源池,當值大于0時,使用者可以鎖定并使用,當小于0時,必須等待有新的資源釋放才能去通路。

        當設定信号量最大值為1時,信号量退化為Mutex。

/** Linux*/
#include <pthread.h>
#include <semaphore.h>

void *thread_func() // 線程函數
{
    for(int i=0;i<50;i++)
    {
        sem_wait(&sem); //V操作,信号量-1
        printf("num %d: Now i come!",i);
        sem_post(&sem); //P操作,信号量+1
        printf("num %d: Now i leave!",i);
    }
}

void main()
{
    sem_t sem;
    sem_init(&sem,0,5); // 信号量值為5 - 了解為保證每個時刻最多5個人進入房間

    pthread_t th[5];
    for(int i=0;i<5;i++)
        pthread_create(&th[i], NULL, thread_func, NULL);
    // 銷毀 pthread_join(&th[i], NULL);

    sem_destroy(&sem); // 關閉unnamed信号量(對應named信号量用sem_close)
}

/** Windows*/
#include "windows.h"
HANDLE hSemp = CreateSemaphore(NULL, 2, 2, NULL); // 建立信号量對象  

WaitForSingleObject(hSemp, INFINITE); // V操作,進入信号量
ReleaseSemaphore(hSemp, 1, NULL); // P操作,釋放信号量
CloseHandle(hSemp); // 關閉信号量對象
           

四. 事件(Event)

        事件 與臨界區一樣,也是 Windows下獨有的一種概念,事件是核心對象,多用于線程間通信,可以跨程序同步,主要用到三個函數:CreateEvent,OpenEvent,SetEvent,ResetEvent。

        OpenEvent與CreateEvent(建立)類似,是打開一個有名事件,我們重點說明另外兩個函數:

        SetEvent:每次觸發後,必有一個或多個處于等待狀态下的線程變成可排程狀态;

        ResetEvent:事件重置,回到未觸發狀态(注:bManualReset可以設定觸發後自動重置)。

#include <windows.h>

HANDLE g_Event = CreateEvent(NULL,false,false,NULL); // HANDLE CreateEvent(LPSECURITY_ATTRIBUTE SlpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);
SetEvent(g_Event);

// 線程綁定的函數傳回值和參數是确定的,而且一定要__stdcall
int __stdcall threadFun()
{
    WaitForSingleObject(g_Event, INFINITE); // 等待事件觸發
    SetEvent(g_Event); // 設定事件為觸發狀态,後面的線程再用
    return 0;
}

int main()
{
    HANDLE th1 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, NULL); // _beginthreadex可以調用__stdcall
    HANDLE th2 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, NULL); // 與CreateThread不同

    WaitForSingleObject(hth1, INFINITE); // - key point 
    WaitForSingleObject(hth2, INFINITE);

    CloseHandle(g_Event); // 關閉事件
    return 0;
}
           

        這樣,整個邏輯變成,哪個線程觸發事件,就可以執行自己的代碼段,而其他線程必須等待該事件釋放,進而達到同步的效果。