Windows線程同步的四種方式


一、基於CRITICAL_SECTION的同步

基於CRITICAL_SECTION的同步中將創建並運用“CRITICAL_SECTION對象”,但這並非內核對象。與其他同步對象相同,它是進入臨界區的一把“鑰匙”。離開時需要上交CRITICAL_SECTION對象。

#include <windows.h>
//初始化函數原型
VOID InitializeCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection   // address of critical 
                                         // section object
);

//銷毀函數原型
VOID DeleteCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection   // pointer to critical 
                                         // section object
);

其中lpCriticalSection,在初始化函數中傳入需要初始化的CRITICAL_SECTION對象的地址值,銷毀函數中傳入需要解除的CRITICAL對象的地址值。

銷毀函數並不是銷毀CRITICAL_SECTION對象的函數,該函數的作用是銷毀CRITICAL_SECTION對象使用過的資源。


獲取及釋放CRITICAL_SECTION對象的函數:

#include <windows.h>
//獲取
VOID EnterCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection   // pointer to critical 
                                         // section object
);

//釋放
VOID LeaveCriticalSection(  
  LPCRITICAL_SECTION lpCriticalSection   // address of critical
                                         // section object
);

lpCriticalSection參數為獲取和釋放CRITICAL_SECTION對象的地址值。


示例:

#include <windows.h>
#include <stdio.h>
#include <process.h>
#include <stdlib.h>
#define NUM_THREAD 50
unsigned WINAPI threadInc(void *arg);
unsigned WINAPI threadDes(void *arg);
long long num = 0;
CRITICAL_SECTION cs;

int main()
{
    HANDLE hHandles[NUM_THREAD];
    int i;
    InitializeCriticalSection(&cs);  //初始化臨界區
    for(i = 0; i < NUM_THREAD; i++)
    {
        if(i % 2)
            hHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
        else
            hHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
    }

    WaitForMultipleObject(NUM_THREAD, hHandles, TRUE, INFINITE);
    DeleteCriticalSection(&cs);  //釋放臨界區
    printf("result: %lld \n", num);

    return 0;
}

unsigned WINAPI threadInc(void *arg)
{
    int i;
    EnterCriticalSection(&cs);  //進入臨界區
    for(i = 0; i < 50000000; i++)
        num += 1;
    LeaveCriticalSection(&cs);  //離開臨界區
    return 0;
}

unsigned WINAPI threadDes(void *arg)
{
    int i;
    EnterCriticalSection(&cs);  //進入臨界區
    for(i = 0; i < 50000000; i++)
        num -= 1;
    LeaveCriticalSection(&cs);  //離開臨界區
    return 0;
}

二、基於互斥量對象的同步

基於互斥量對象的同步方法與給予CRITICAL_SECTION對象的同步方法類似。

創建互斥量對象的函數:

HANDLE CreateMutex(  
  LPSECURITY_ATTRIBUTES lpMutexAttributes,
                       // pointer to security attributes
  BOOL bInitialOwner,  // flag for initial ownership
  LPCTSTR lpName       // pointer to mutex-object name
);

//參數意義:
//lpMutextAttributes 傳遞安全相關的配置信息,使用默認安全設置時可以傳遞NULL
//bInitialOwner 如果為TRUE,則創建出的互斥量對象屬於調用該函數的線程,同時進入non-signaled狀態;
//如果為FALSE,則創建出的互斥量對象不屬於任何線程,此時狀態為signaled
//lpName 用於命名互斥量對象。傳入NULL時創建無名的互斥量對象

可以看出,如果互斥量對象不屬於任何擁有者,則將進入signaled狀態,利用該特點進行同步。另外,互斥量屬於內核對象,所以通過如下函數銷毀:

BOOL CloseHandle(
    HANDLE hObject  //要銷毀內核對象的句柄
);

獲取和釋放互斥量的函數:

//獲取函數  Windows線程創建中介紹的此函數,用於針對單個內核對象驗證signaled。
DWORD WaitForSingleObject(  
  HANDLE hHandle,        // handle to object to wait for  
  DWORD dwMilliseconds   // time-out interval in milliseconds  
);

//釋放互斥量
BOOL ReleaseMutex(
    HANDLE hMutex  //需要釋放的對象的句柄
);

互斥量被某一線程獲取時為non-signaled狀態,釋放時進入signaled狀態。因此,可以利用WaitForSingleObject函數驗證互斥量是否已分配。

互斥量在WaitForSingleObject函數返回時自動進入non-signaled狀態,因為它是“auto-reset”模式的內核對象。

#include <windows.h>  
#include <process.h>  
#include <stdio.h>  
#include <stdlib.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(NULL, FALSE, NULL);  //創建互斥量,此時為signaled狀態  
    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 < 50000000; i++)  
        num += 1;  
    ReleaseMutex(hMutex);  //釋放,離開時上交鑰匙  
    return 0;  
}  
  
unsigned WINAPI threadDes(void * arg)  
{  
    int i;  
    WaitForSingleObject(hMutex, INFINITE);     
    for (i = 0; i < 50000000; i++)  
        num -= 1;  
    ReleaseMutex(hMutex);  
    return 0;  
}

三、基於信號量對象的同步

創建與銷毀函數:

//創建信號量對象
HANDLE CreateSemaphore(  
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,  
                       // pointer to security attributes  
  LONG lInitialCount,  // initial count  
  LONG lMaximumCount,  // maximum count  
  LPCTSTR lpName       // pointer to semaphore-object name  
);

//參數意義:  
//lpSemaphoreAttributes  安全配置信息,采用默認安全設置時NULL  
//lInitialCount  指定信號量的初始值,應大於0小於lMaximumCount  
//lMaximumCount  信號量的最大值。該值為1時,信號量變為只能表示0和1的二進制信號量  
//lpName  用於命名信號量對象。傳遞NULL時創建無名的信號量對象  
//銷毀信號量同樣使用CloseHandle()函數

可以利用“信號量值為0時進入non-signaled狀態,大於0時進入signaled狀態”的特性進行同步。向lInitialCount參數傳遞0時,創建non-signaled狀態的信號量對象。而向lMaximumCount傳入3時,信號量最大值為3,因此可以實現3個線程同時訪問臨界區時的同步。


釋放信號量對象的函數:

//釋放信號量
BOOL ReleaseSemaphore(  
  HANDLE hSemaphore,   // handle to the semaphore object  
  LONG lReleaseCount,  // amount to add to current count  
  LPLONG lpPreviousCount   // address of previous count  
);

//參數意義:  
//hSemaphore  傳遞需要釋放的信號量對象。  
//lReleaseCount  釋放以為着信號量值的增加,通過該參數可以指定增加的值。超過最大值則不增加,返回FALSE  
//lpPreviousCount  用於保存之前值得變量地址,不需要是可傳遞NULL

信號量對象大於0時成為signaled對象,為0時成為non-signaled對象。因此,調用WaitForSingleObject函數時,信號量大於0的情況下才會返回。返回的同時將信號量的值減1,同時進入non-signaled狀態。

#include <windows.h>  
#include <process.h>  
#include <stdio.h>  
  
unsigned WINAPI Read(void * arg);  
unsigned WINAPI Accu(void * arg);  
  
static HANDLE semOne;  
static HANDLE semTwo;  
static int num;  
  
int main(int argc, char *argv[])  
{  
    HANDLE hThread1, hThread2;  

    //創建信號量對象,設置為0進入non-signaled狀態  
    semOne = CreateSemaphore(NULL, 0, 1, NULL);  

    //創建信號量對象,設置為1進入signaled狀態  
    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); //銷毀  

    return 0;  
}  

unsigned WINAPI Read(void * arg)  
{  
    int i;  
    for (i = 0; i < 5; i++)  
    {  
        fputs("Input num: ", stdout);  

        //臨界區的開始 signaled狀態  
        WaitForSingleObject(semTwo, INFINITE);  
        scanf("%d", &num);  

        //臨界區的結束 non-signaled狀態  
        ReleaseSemaphore(semOne, 1, NULL);  
    }  
    return 0;  
}  

unsigned WINAPI Accu(void * arg)  
{  
    int sum = 0, i;  
    for (i = 0; i < 5; i++)  
    {  
        //臨界區的開始 non-signaled狀態  
        WaitForSingleObject(semOne, INFINITE);  
        sum += num;  
        //臨界區的結束 signaled狀態  
        ReleaseSemaphore(semTwo, 1, NULL);  
    }  
    printf("Result: %d \n", sum);  
    return 0;
}

四、基於事件對象的同步

事件同步對象與前2種同步方法相比有很大不同,區別在於:該方法下創建對象時,可以在自動non-signaled狀態運行的auto-reset模式和與之相反的manual-reset模式中任選其一。而事件對象的主要特點是可以創建manual-reset模式的對象。

創建事件對象的函數:

HANDLE CreateEvent(  
  LPSECURITY_ATTRIBUTES lpEventAttributes,  
                      // pointer to security attributes  
  BOOL bManualReset,  // flag for manual-reset event  
  BOOL bInitialState, // flag for initial state  
  LPCTSTR lpName      // pointer to event-object name  
);  
   
參數說明:  
//lpEventAttributes  安全配置相關參數,采用默認安全配置時傳入NULL  
//bManualReset  傳入TRUE時創建manual-reset模式的事件對象,傳入FALSE時創建auto-reset模式的事件對象  
//bInitialState  傳入TRUE時創建signaled狀態,傳入FALSE時創建non-signaled狀態的事件對象  
//lpName  用於命名事件對象。傳遞NULL時創建無名的事件對象

當第二個參數傳入TRUE時將創建manual-reset模式的事件對象,此時即使WaitForSingleObject函數返回也不會回到non-signaled狀態。因此,在這種情況下,需要通過如下2個函數明確更改對象狀態。

BOOL ResetEvent(
    HANDLE hEvent  //to the non-signaled
);

BOOL SetEvent(
    HANDLE hEvent  //to the signaled
);

傳遞事件對象句柄並希望改為non-signed狀態時,應調用ResetEvent函數。如果希望改為signaled狀態,則可以調用SetEvent函數。

示例:

#include <windows.h>  
#include <stdio.h>  
#include <process.h>  
#define STR_LEN 100  
   
unsigned WINAPI NumberOfA(void *arg);  
unsigned WINAPI NumberOfOthers(void *arg);  
   
static char str[STR_LEN];  
static HANDLE hEvent;  
   
int main(int argc, char *argv[])  
{  
    HANDLE hThread1, hThread2;  
   
    //以non-signaled創建manual-reset模式的事件對象  
    hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);  
   
    hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);  
    hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);  
   
    fputs("Input string: ", stdout);  
    fgets(str, STR_LEN, stdin);  
   
    //讀入字符串后改為signaled狀態  
    SetEvent(hEvent);             
   
    WaitForSingleObject(hThread1, INFINITE);  
    WaitForSingleObject(hThread2, INFINITE);  
   
    //non-signaled 如果不更改,對象繼續停留在signaled  
    ResetEvent(hEvent);           
    CloseHandle(hEvent);  
   
    return 0;  
}  
   
unsigned WINAPI NumberOfA(void *arg)  
{  
    int i, cnt = 0;  
    WaitForSingleObject(hEvent, INFINITE);  
    for (i = 0; str[i] != 0; i++)  
    {  
        if (str[i] == 'A')  
            cnt++;  
    }  
    printf("Num of A: %d \n", cnt);  
    return 0;  
}  
   
unsigned WINAPI NumberOfOthers(void *arg)  
{  
    int i, cnt = 0;  
    WaitForSingleObject(hEvent, INFINITE);  
    for (i = 0; str[i] != 0; i++)  
    {  
        if (str[i] != 'A')  
            cnt++;  
    }  
    printf("Num of others: %d \n", cnt - 1);  
    return 0;  
}


免責聲明!

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



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