线程同步的四种方法

一、基于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;  
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 引用自多线程编程指南应用程序里面多个线程的存在引发了多个执行线程安全访问资源的潜在问题。两个线程同时修改同一资源有...
    Mitchell阅读 1,973评论 1 7
  • LINUX 基础知识 1、线程的概念 上下文切换 : 运行程序前需要将相应进程信息读入内存,如果运行进程A后需要紧...
    Ycres阅读 752评论 0 2
  • 接着上上节 thread ,本节主要介绍mutex的内容,练习代码地址。<mutex>:该头文件主要声明了与互斥量...
    jorion阅读 12,446评论 2 4
  • 主要讲关键段,事件,互斥量,信号量。 学习CSDN上MoreWindows博客《秒杀多线程》系列。 CreateT...
    静候那一米阳光阅读 321评论 0 1
  • 文/清音 昨夜一场突如其来的雷雨、闪电、狂风。听着窗外呼呼作响。 早晨窗外的雨,还是细密不停歇,如同这个世道的倒影...
    琵琶清音阅读 242评论 4 4