日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C++實現線程同步的四種方式總結_C 語言

作者:霸道小明 ? 更新時間: 2022-12-10 編程語言

內核態

互斥變量?

互斥對象包含一個使用數量,一個線程ID和一個計數器。其中線程ID用于標識系統中的哪個線程當前擁有互斥對象,計數器用于指明該線程擁有互斥對象的次數。

創建互斥對象:調用函數CreateMutex。調用成功,該函數返回所創建的互斥對象的句柄。

請求互斥對象所有權:調用函數WaitForSingleObject函數。線程必須主動請求共享對象的所有權才能獲得所有權。

釋放指定互斥對象的所有權:調用ReleaseMutex函數。線程訪問共享資源結束后,線程要主動釋放對互斥對象的所有權,使該對象處于已通知狀態。

創建互斥對象函數

HANDLE
WINAPI
CreateMutexW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,   //指向安全屬性
    _In_ BOOL bInitialOwner,   //初始化互斥對象的所有者  TRUE 立即擁有互斥體
    _In_opt_ LPCWSTR lpName    //指向互斥對象名的指針  L“Bingo”
);
  • 第一個參數表示安全屬性,這是每一個創建內核對象都會有的參數,NULL表示默認安全屬性
  • 第二個參數表示互斥對象所有者,TRUE立即擁有互斥體
  • 第三個參數表示指向互斥對象的指針?

代碼示例

下面這段程序聲明了一個全局整型變量,并初始化為0。一個線程函數對這個變量進行+1操作,執行50000次;另一個線程函數對這個變量-1操作,執行50000次。兩個線程函數各創建25個。因為我們使用了互斥變量,50個線程會按照一定順序對這變量操作,因此最后結果為0。?

#include <stdio.h>
#include <windows.h>
#include <process.h>
 
#define NUM_THREAD    50
unsigned WINAPI threadInc(void* arg);
unsigned WINAPI threadDes(void* arg);
long long num = 0;
HANDLE hMutex;
 
int main() {
    //內核對象數組
    HANDLE tHandles[NUM_THREAD];
    int i;
    //創建互斥信號量
    hMutex = CreateMutex(0, FALSE, NULL);
    printf("sizeof long long: %d \n", sizeof(long long));
    for (i = 0; i < NUM_THREAD; i++) {
        if (i % 2)
            tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
        else
            tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
    }
 
    WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
    //關閉互斥對象
    CloseHandle(hMutex);
    printf("result: %lld \n", num);
    return 0;
}
 
unsigned WINAPI threadInc(void* arg){
    int i;
    //請求使用
    WaitForSingleObject(hMutex, INFINITE);
    for (i = 0; i < 500000; i++)
        num += 1;
    //釋放
    ReleaseMutex(hMutex);
    return 0;
}
unsigned WINAPI threadDes(void* arg){
    int i;
    //請求
    WaitForSingleObject(hMutex, INFINITE);
    for (i = 0; i < 500000; i++)
        num -= 1;
    //釋放
    ReleaseMutex(hMutex);
    return 0;
}

事件對象

事件對象也屬于內核對象,它包含以下三個成員:

  • 使用計數;
  • 用于指明該事件是一個自動重置的事件還是一個人工重置的事件的布爾值;
  • 用于指明該事件處于已通知狀態還是未通知狀態的布爾值。

事件對象有兩種類型:人工重置的事件對象和自動重置的事件對象。這兩種事件對象的區別在于當人工重置的事件對象得到通知時,等待該事件對象的所有線程均變為可調度線程;而當一個自動重置的事件對象得到通知時,等待該事件對象的線程中只有一個線程變為可調度線程。

1.創建事件對象

調用CreateEvent函數創建或打開一個命名的或匿名的事件對象。

HANDLE CreateEvent(   
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全屬性   
BOOL bManualReset,   // 復位方式  TRUE 必須用ResetEvent手動復原  FALSE 自動還原為無信號狀態
BOOL bInitialState,   // 初始狀態   TRUE 初始狀態為有信號狀態  FALSE 無信號狀態
LPCTSTR lpName     //對象名稱  NULL  無名的事件對象 
);
  • 第一個參數表示安全屬性,這是創建內核對象函數都有的一個參數,NULL表示默認安全屬性
  • 第二個參數表示復位方式,如果是TRUE,則必須手動調用ResetEvent函數復位,FALSE則表示自動還原
  • 第三個參數表示初始狀態,TRUE表示初始為有信號狀態,FALSE為無信號
  • 第四個參數表示對象名稱,NULL表示無名的事件對象

2. 設置事件對象狀態

調用SetEvent函數把指定的事件對象設置為有信號狀態。

3. 重置事件對象狀態

調用ResetEvent函數把指定的事件對象設置為無信號狀態。

4. 請求事件對象

?線程通過調用WaitForSingleObject函數請求事件對象。

代碼示例?

下面這段程序是一段火車售票:線程A和B會不停的購票直到票數小于0,執行完畢。在判斷票數前會先申請事件對象,購票結束或者票數小于0時則會釋放事件對象(事件對象置位有信號)。因為我們使用了事件對象。兩個線程會按某一順序購票,直到票數小于0。

#include<iostream>
#include<Windows.h>
#include<process.h>
using namespace std;
 
//火車站賣票
int iTickets = 100;//總票數
HANDLE g_hEvent;
 
 
unsigned WINAPI SellTicketA(void* lpParam) {
 
    while (true) {
        WaitForSingleObject(g_hEvent, INFINITE);
        if (iTickets > 0) {
            Sleep(1);
            printf("A買了一張票,剩余%d\n", iTickets--);
        }
        else {
            SetEvent(g_hEvent);
            break;
        }
        SetEvent(g_hEvent);
    }
    return 0;
}
 
unsigned WINAPI SellTicketB(void* lpParam) {
    while (true) {
        WaitForSingleObject(g_hEvent, INFINITE);
        if (iTickets > 0) {
            Sleep(1);
            printf("B買了一張票,剩余%d\n", iTickets--);
        }
        else {
            SetEvent(g_hEvent);
            break;
        }
        SetEvent(g_hEvent);
    }
    return 0;
}
 
int main() {
 
    HANDLE hThreadA, hThreadB;
    hThreadA = (HANDLE)_beginthreadex(NULL, 0, SellTicketA, NULL, 0, NULL);
    hThreadB = (HANDLE)_beginthreadex(NULL, 0, SellTicketB, NULL, 0, NULL);
 
    CloseHandle(hThreadA);
    CloseHandle(hThreadB); 
 
    g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    SetEvent(g_hEvent);
    Sleep(4000);
    CloseHandle(g_hEvent);
    system("pause");
    return 0;
}

資源信號量

信號量(semaphore)是操作系統用來解決并發中的互斥和同步問題的一種方法。與互斥量不同的地方是,它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。

創建信號量函數

HANDLE    WINAPI    
CreateSemaphoreW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,  // Null 安全屬性
    _In_ LONG lInitialCount,  //初始化時,共有多少個資源是可以用的。 0:未觸發狀//態(無信號狀態),表示沒有可用資源
    _In_ LONG lMaximumCount,  //能夠處理的最大的資源數量   3
    _In_opt_ LPCWSTR lpName   //NULL 信號量的名稱
);
  • 第一個參數表示安全屬性,這是創建內核對象函數都會有的參數,NULL表示默認安全屬性
  • 第二個參數表示初始時有多少個資源可用,0表示無任何資源(未觸發狀態)
  • 第三個參數表示最大資源數
  • 第四個參數表示信號量的名稱,NULL表示無名稱的信號量對象

增加/釋放信號量

ReleaseSemaphore(
    _In_ HANDLE hSemaphore,   //信號量的句柄
    _In_ LONG lReleaseCount,   //將lReleaseCount值加到信號量的當前資源計數上面 0-> 1
    _Out_opt_ LPLONG lpPreviousCount  //當前資源計數的原始值
);
  • 第一個參數表示信號量句柄,也就是調用創建信號量函數時返回的句柄
  • 第二個參數表示釋放的信號量個數,該值必須大于0,但不能大于信號量的最大計數
  • 第三個參數表示指向要接收信號量的上一個計數的變量的指針。如果不需要上一個計數, 則此參數可以為NULL 。

關閉句柄

CloseHandle(
    _In_ _Post_ptr_invalid_ HANDLE hObject
);

代碼示例

下面這段程序創建了兩個信號資源,其最大資源都為1;一個初始資源為0,另一個初始資源為1。線程中的for循環每執行一次會將另一個要申請的信號資源的可用資源數+1。因此程序的執行結果為兩個線程中的for循環交替執行。

#include<iostream>
#include<Windows.h>
#include<process.h>
using namespace std;
 
static HANDLE semOne;
static HANDLE semTwo;
static int num;
 
/*
* 信號資源semOne初始為0,最大1個資源可用
* 信號資源semTwo初始為1,最大1個資源可用
*/
 
unsigned WINAPI Read(void* arg) {
    int i;
    for (i = 0; i < 5; i++) {
        fputs("Input num:\n", stdout);
        printf("begin read\n");
        WaitForSingleObject(semTwo, INFINITE);
        printf("beginning read\n");
        scanf("%d", &num);
        ReleaseSemaphore(semOne, 1, NULL);
    }
    return 0;
}
 
unsigned WINAPI Accu(void* arg) {
    int sum = 0, i;
    for (i = 0; i < 5; ++i) {
        printf("begin Accu\n");
        WaitForSingleObject(semOne, INFINITE);
        printf("beginning Accu\n");
        sum += num;
        printf("sum=%d\n", sum);
        ReleaseSemaphore(semTwo, 1, NULL);
    }
    return 0;    
}
 
int main() {
    HANDLE hThread1, hThread2;
    semOne = CreateSemaphore(NULL, 0, 1, NULL);//初始值沒有可用資源
    semTwo = CreateSemaphore(NULL, 1, 1, NULL);//初始值有一個可用資源
 
 
    hThread1 = (HANDLE)_beginthreadex(NULL, 0, Read, NULL, 0, NULL);
    hThread2 = (HANDLE)_beginthreadex(NULL, 0, Accu, NULL, 0, NULL);
    WaitForSingleObject(hThread1, INFINITE);
    WaitForSingleObject(hThread2, INFINITE);
 
    CloseHandle(semOne);
    CloseHandle(semTwo);
    system("pause");
    return 0;
}

用戶態

關鍵代碼

關鍵代碼段,也稱為臨界區,工作在用戶方式下。它是指一個小代碼段,在代碼能夠執行前,它必須獨占對某些資源的訪問權。通常把多線程中訪問同一種資源的那部分代碼當做關鍵代碼段。

1.初始化關鍵代碼段

調用InitializeCriticalSection函數初始化一個關鍵代碼段

InitialzieCriticalSection(
    _Out_ LPRRITICAL_SECTION lpCriticalSection
);

該函數只有一個指向CRITICAL_SECTION結構體的指針。在調用InitializeCriticalSection函數之前,首先需要構造一個CRITICAL_SCTION結構體類型的對象,然后將該對象的地址傳遞給InitializeCriticalSection函數。

2進入關鍵代碼

VOID
WINAPI
EnterCriticalSection(
    _Inout_ LPCRITICAL_SECTION lpCriticalSection
);

調用EnterCriticalSection函數,以獲得指定的臨界區對象的所有權,該函數等待指定的臨界區對象的所有權,如果該所有權賦予了調用線程,則該函數就返回;否則該函數會一直等待,從而導致線程等待。

3.退出關鍵代碼段

VOID
WINAPI
LeaveCriticalSection(
    _Inout_ LPCRITICAL_SECTION lpCriticalSection
);

線程使用完臨界區所保護的資源之后,需要調用LeaveCriticalSection函數,釋放指定的臨界區對象的所有權。之后,其他想要獲得該臨界區對象所有權的線程就可以獲得該所有權,從而進入關鍵代碼段,訪問保護的資源。

4.刪除臨界區

WINBASEAPI
VOID
WINAPI
DeleteCriticalSection(
    _Inout_ LPCRITICAL_SECTION lpCriticalSection
);

當臨界區不再需要時,可以調用DeleteCriticalSection函數釋放該對象,該函數將釋放一個沒有被任何線程所擁有的臨界區對象的所有資源。

程序實例:

下面這段程序同樣也是火車售票,其工作邏輯與上面的事件對象基本吻合。

#include<iostream>
#include<Windows.h>
#include<process.h> 
using namespace std;
 
int iTickets = 100;
CRITICAL_SECTION g_cs;
  
//A窗口
DWORD WINAPI SellTicketA(void* lpParam) {
    while (1) {
        EnterCriticalSection(&g_cs);//進入臨界區
        if (iTickets > 0) {
            Sleep(1);
            iTickets--;
            printf("A買了一張票,剩余票數為:%d\n", iTickets);
            LeaveCriticalSection(&g_cs);
        }
        else {
            LeaveCriticalSection(&g_cs);
            break;
        }
    }
    return 0;
}
 
//B窗口
DWORD WINAPI SellTicketB(void* lpParam) {
    while (1) {
        EnterCriticalSection(&g_cs);
        if (iTickets > 0) {
            Sleep(1);
            iTickets--;
            printf("B買了一張票,剩余票數為:%d\n", iTickets);
            LeaveCriticalSection(&g_cs);
        }
        else {
            LeaveCriticalSection(&g_cs);
            break;
        }
    }
    return 0;
}
 
 
int main() {
    HANDLE hThreadA, hThreadB;
    hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL);
    hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL);
 
    CloseHandle(hThreadA);
    CloseHandle(hThreadB);
        
    InitializeCriticalSection(&g_cs);//初始化關鍵代碼
    Sleep(1000);
 
    DeleteCriticalSection(&g_cs);
    system("pause");
    return 0;
}

原文鏈接:https://blog.csdn.net/qq_54169998/article/details/127786699

欄目分類
最近更新