1. 程式人生 > >C++多執行緒 互斥鎖 訊號量 事件 臨界區

C++多執行緒 互斥鎖 訊號量 事件 臨界區

一、互斥鎖

1、先熟悉熟悉API

1,建立互斥鎖,並反正一個控制代碼
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全屬性的指標
BOOLbInitialOwner, // 初始化互斥物件的所有者,一般設定為FALSE
LPCTSTRlpName // 互斥物件名
);

2,釋放互斥物件的控制權
BOOL ReleaseMutex(
HANDLE hMutex //已建立Mutex的控制代碼
);

3,開啟互斥鎖,返回控制代碼
HANDLE OpenMutex(
DWORDdwDesiredAccess, // 互斥體的訪問許可權
BOOLbInheritHandle, //如希望子程序能夠繼承控制代碼,則為TRUE LPCTSTRlpName // 互斥鎖名字 ); 4,等待目標返回 DWORD WaitForSingleObject( HANDLE hHandle, //目標控制代碼 DWORD dwMilliseconds //等待時間 ,毫秒記,INFINITE為永久等待 );

一個簡單的互斥鎖例子來實現多執行緒同步

#include<Windows.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
static
int g_count = 100; class Lock { public: Lock(char*szName) { hd = OpenMutex(MUTEX_ALL_ACCESS, NULL, (LPCWSTR)szName); WaitForSingleObject(hd, INFINITE); Sleep(1500);//測試用:等待1.5秒,正式類中不應有 } ~Lock() { ReleaseMutex(hd); } static bool InitLock(char*szName) { if
(!OpenMutex(MUTEX_ALL_ACCESS, NULL, (LPCWSTR)szName)) { if (!CreateMutex(NULL, NULL, (LPCWSTR)szName)) return false; return true; } return false; } HANDLE hd; }; UINT address1(LPVOID lparam) { while (1) { Lock lc("_temp_lock"); cout << g_count-- << "---" << "address1" << endl; } } UINT address2(LPVOID lparam) { while (1) { Lock lc("_temp_lock"); cout << g_count-- << "---" << "address2" << endl; } } int main(int argc, char*argv[]) { if(!Lock::InitLock("_temp_lock"))//建立互斥鎖失敗 return -1; CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)address1, NULL, NULL, NULL);//開啟執行緒1 CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)address2, NULL, NULL, NULL);//開啟執行緒2 system("pause"); return 0; }

原理淺析:Lock::InitLock()來建立互斥鎖,在本demo中,同步是按照CreateThread的建立順序。建立了一個Lock類,當生成物件的時候就等待,一旦當互斥鎖沒有控制權,等待即結束,便向下執行,物件析構的時候就釋放控制權,以此類推迴圈。
ps:mutex作用範圍在系統層,使用mutex效率並不是太高,使用臨界區(作用範圍在程序)效率比較高

二、訊號量

1,先熟悉熟悉API

CreateSemaphore() 建立一個訊號量  

OpenSemaphore() 開啟一個訊號量  

ReleaseSemaphore() 釋放訊號量 

WaitForSingleObject() 等待訊號量  

2,舉個簡單的小例子

#include <stdio.h>  
#include <Windows.h>  


#define THREAD_NUM 20
#define SEM_NAME "THREAD_SEMAPHORE"

HANDLE g_hSem = NULL;
HANDLE g_hThread[THREAD_NUM];


void WINAPI ThreadFun(void* param)
{
    printf("進入執行緒: %u,並等待\n", GetCurrentThreadId());
    WaitForSingleObject(g_hSem, INFINITE);

    printf("執行緒: %u 獲得訊號量\n", GetCurrentThreadId());

    long dwSem = 0;
    if (!ReleaseSemaphore(g_hSem, 1, &dwSem))
        return;

    printf("目前資源數:%u\n", dwSem);
}

int  main(int argc, char*argv[])
{
    g_hSem = CreateSemaphoreA(NULL, 0, 5, SEM_NAME);
    g_hSem = OpenSemaphoreA(SEMAPHORE_MODIFY_STATE, FALSE, SEM_NAME);

    for (int i = 0; i < THREAD_NUM; ++i)
    {
        DWORD dwThreadID = 0;
        g_hThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun, NULL, 0, &dwThreadID);
    }

    WaitForMultipleObjects(THREAD_NUM, g_hThread, TRUE, INFINITE);

    printf("全部執行完了\n");
    return 0;
}

ps:訊號量可以說在平常的使用中用的比較少,一般的用途在控制併發執行緒數量,搶佔資源問題。

三、事件

1,先熟悉熟悉API

HANDLE CreateEvent( //建立事件
LPSECURITY_ATTRIBUTES lpEventAttributes,// 安全屬性
BOOL bManualReset,// 復位方式
BOOL bInitialState,// 初始狀態
LPCTSTR lpName // 物件名稱
);

HANDLE OpenEvent( //開啟事件
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);

BOOL ResetEvent(//事件物件設定為無訊號狀態。
HANDLE hEvent
);

BOOL SetEvent(HANDLE hEvent);//事件物件設定為有訊號狀態。 

BOOL PulseEvent(HANDLE hEvent)//事件物件設定為有訊號狀態,脈衝一個事件

WaitForSingleObject() 等待訊號量  

2,舉個簡單的小例子

#include <stdio.h>
#include <Windows.h>
HANDLE g_hEvent;

UINT address1(LPVOID lparam)
{
    printf("進入執行緒了,等待5秒\n");
    WaitForSingleObject(g_hEvent, INFINITE);
    printf("等待結束了,向下執行了!\n");
    return 0;
}

UINT address2(LPVOID lparam)
{
    Sleep(5000);
    SetEvent(g_hEvent);
    return 0;
}

int main(int argc, char*argv[])
{
    g_hEvent=CreateEvent(NULL, true, FALSE, NULL);
    ResetEvent(g_hEvent);

    CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)address1, NULL, NULL, NULL);//開啟執行緒1
    CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)address2, NULL, NULL, NULL);//開啟執行緒2

    system("pause");
    return 0;
}

ps:本小例模擬了的兩個執行緒間的同步,address2來控制address1的執行,事件在多執行緒程式中的應用比較多,比較重要,定要熟練掌握。

四、臨界區

1,先熟悉熟悉API

void InitializeCriticalSection(//初始化臨界區
 LPCRITICAL_SECTION lpCriticalSection); 

void WINAPI DeleteCriticalSection( //刪除臨界區  
_Inout_ LPCRITICAL_SECTION lpCriticalSection);

VOID WINAPI EnterCriticalSection( //進入臨界區  
    __inout LPCRITICAL_SECTION lpCriticalSection
); 

VOID WINAPI LeaveCriticalSection(//離開臨界區
    _Inout_ LPCRITICAL_SECTION lpCriticalSection
    );

2,舉個簡單的小例子

#include <stdio.h>
#include <Windows.h>

CRITICAL_SECTION g_cs;
int g_nIndex = 20;

UINT address1(LPVOID lparam)
{
    while(true)
    {
        if (10 == g_nIndex)
            return 1;
        EnterCriticalSection(&g_cs);
        printf("address111執行緒,index=%d\n",g_nIndex);
        g_nIndex--;
        LeaveCriticalSection(&g_cs);
    }
    return 0;

}

UINT address2(LPVOID lparam)
{
    while (true)
    {
        if (0 == g_nIndex)
            return 1;
        EnterCriticalSection(&g_cs);
        printf("address222執行緒,index=%d\n", g_nIndex);

        --g_nIndex;
        LeaveCriticalSection(&g_cs);
    }
    return 0;
}
int main(int argc, char*argv[])
{
    InitializeCriticalSection(&g_cs);

    CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)address1, NULL, NULL, NULL);//開啟執行緒1
    CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)address2, NULL, NULL, NULL);//開啟執行緒2

    system("pause");
    DeleteCriticalSection(&g_cs);
    return 0;
}

ps:小例中,address1被臨界區一直佔用到到address1退出,address2才能進入臨界區,進行直到退出,與mutex交替執行不同,臨界區比較簡單且最易用。
這裡寫圖片描述