使用互斥量进行线程同步,与关键段和事件的区别

时间:2022-11-18 14:43:50
 1 #include <iostream>
 2 #include <process.h>
 3 #include <windows.h>
 4 #include <string>
 5 using std::cout;
 6 using std::endl;
 7 using std::string;
 8 
 9 const int num=2;
10 int count;
11 unsigned __stdcall ThreadFun(void* par);
12 
13 int main()
14 {
15     count=0;
16     HANDLE handles[num];
17     for(int i=0;i<num;++i)
18     {
19         handles[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,NULL,0,0);
20     }
21     for(int i=0;i<num;++i)
22         CloseHandle(handles[i]);
23     system("PAUSE");
24     return 0;
25 }
26 unsigned __stdcall ThreadFun(void* par)
27 {
28     Sleep(1000);
29     for(int i=0;i<10;++i)
30         cout<<"cout:"<<++count<<endl;
31     count=0;
32     return 0;
33 }

使用互斥量进行线程同步,与关键段和事件的区别

上面的代码在多线程环境中会争夺执行,导致输出结果不是预期的,下面我们先用互斥量来完善下

互斥量包含计数,线程ID,递归计数,互斥量与关键段的行为完全相同,互斥量是内核对象,而关键段是用户模式(对资源争夺激烈会导致等待关键段的线程进入内核模式等待),重点就是线程ID和递归计数。
这2个字段导致互斥量的行为和关键段相似,和事件内核对象不同。

 1 #include <iostream>
 2 #include <process.h>
 3 #include <windows.h>
 4 #include <string>
 5 using std::cout;
 6 using std::endl;
 7 using std::string;
 8 
 9 const int num=2;
10 int count;
11 HANDLE ThreadMutex;
12 unsigned __stdcall ThreadFun(void* par);
13 
14 int main()
15 {
16     count=0;
17     HANDLE handles[num];
18     //互斥量对象的线程ID和递归计数初始化为0,互斥量想在不为任何线程占用
19     ThreadMutex=CreateMutex(NULL,FALSE,NULL);
20     for(int i=0;i<num;++i)
21     {
22         handles[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,NULL,0,0);
23     }
24     //等待所有线程执行完毕
25     WaitForMultipleObjects(num,handles,TRUE,INFINITE);
26     for(int i=0;i<num;++i)
27         CloseHandle(handles[i]);
28     CloseHandle(ThreadMutex);
29     system("PAUSE");
30     return 0;
31 }
32 unsigned __stdcall ThreadFun(void* par)
33 {
34     //等待互斥量对象(内部检查互斥量对象的线程ID是是否为0,0为触发状态)
35     //如果线程ID不为0,那么调用线程将进入等待状态
36     WaitForSingleObject(ThreadMutex,INFINITE);
37     for(int i=0;i<10;++i)
38         cout<<"cout:"<<++count<<endl;
39     count=0;
40     //释放对资源的所有权,将互斥量对象的线程ID和递归计数设置成0
41     ReleaseMutex(ThreadMutex);
42     return 0;
43 }

使用互斥量进行线程同步,与关键段和事件的区别

结果和我们预期的一样了,从上面代码感觉互斥量和事件对象很像,但是事实却不是这样,下面我们分析下互斥量,关键段,事件这3个的区别。

稍微修改下代码,将WaitForMultipleObjects(num,handles,TRUE,INFINITE);放到创建线程之前,然后在线程方法里面触发互斥量。

#include <iostream>
#include <process.h>
#include <windows.h>
#include <string>
using std::cout;
using std::endl;
using std::string;

const int num=2;
int count;
HANDLE ThreadMutex;
unsigned __stdcall ThreadFun(void* par);

int main()
{
    count=0;
    HANDLE handles[num];
    //互斥量对象的线程ID和递归计数初始化为0,互斥量想在不为任何线程占用
    ThreadMutex=CreateMutex(NULL,FALSE,NULL);
    for(int i=0;i<num;++i)
    {
        WaitForSingleObject(ThreadMutex,INFINITE);
        handles[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,NULL,0,0);
    }
    //等待所有线程执行完毕
    WaitForMultipleObjects(num,handles,TRUE,INFINITE);
    for(int i=0;i<num;++i)
        CloseHandle(handles[i]);
    CloseHandle(ThreadMutex);
    system("PAUSE");
    return 0;
}
unsigned __stdcall ThreadFun(void* par)
{
    //等待互斥量对象(内部检查互斥量对象的线程ID是是否为0,0为触发状态)
    //如果线程ID不为0,那么调用线程将进入等待状态
    //WaitForSingleObject(ThreadMutex,INFINITE);
    for(int i=0;i<10;++i)
        cout<<"cout:"<<++count<<endl;
    count=0;
    //释放对资源的所有权,将互斥量对象的线程ID和递归计数设置成0
    cout<<"ReleaseMutex:"<<ReleaseMutex(ThreadMutex)<<endl;
    return 0;
}

 

使用互斥量进行线程同步,与关键段和事件的区别

结果不是预期的,输出的ReleaseMutex方法的调用结果为0(FALSE),ReleaseMutex方法调用失败,原因是互斥量和关键段一样都有线程所有权的概念,互斥量和关键段都是绑定到执行线程。

互斥量的WaitForSingleObject-->ReleaseMute,关键段的EnterCriticalSection-->LeaveCriticalSection,都是必须在同一个线程内执行,线程A拥有了资源的所有权,那么释放所有权也必须由线程A来执行,所以上面的代码ReleaseMute方法执行失败,因为WaitForSingleObject在主线程内执行的,所以互斥量的线程ID字段的值就是主线程的ID,而方法线程来执行ReleaseMute就会失败,下面是关键段的实现互斥的代码。

unsigned __stdcall ThreadFun(void* par)
{
    EnterCriticalSection(&cs);
    for(int i=0;i<10;++i)
        cout<<"cout:"<<++count<<endl;
    count=0;
    LeaveCriticalSection(&cs);
    return 0;
}

而事件内核对象就比较灵活,互斥量和关键段都是线程互斥(当资源可用,所有等待的线程无法按照顺序来执行,由系统决定线程所有权),而事件是同步(可以控制线程的执行顺序),事件内核对象可以在主线程调用WaitForSingleObject来等待事件对象激活,在执行线程中设置对象的状态(SetEvent,ResetEvent)。

下面用事件来实现上面代码的同步

#include <iostream>
#include <process.h>
#include <windows.h>
#include <string>
using std::cout;
using std::endl;
using std::string;

const int num=2;
int count;
HANDLE ThreadEvent;
unsigned __stdcall ThreadFun(void* par);

int main()
{
    count=0;
    HANDLE handles[num];
    //创建自动重置,未激活的事件内核对象
    ThreadEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
    for(int i=0;i<num;++i)
    {
        //创建执行线程,顺利创建第一个线程,然后程序会在WaitForSingleObject处等待,
        //直到第一个线程将事件状态设置为激活状态,才能继续下去
        handles[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,NULL,0,0);
        //注意:主线程内等待事件被激活
        WaitForSingleObject(ThreadEvent,INFINITE);
        //这里会自动调用ResetEvent()将事件设置为未激活
    }
    //等待所有线程执行完毕
    WaitForMultipleObjects(num,handles,TRUE,INFINITE);
    for(int i=0;i<num;++i)
        CloseHandle(handles[i]);
    CloseHandle(ThreadEvent);
    system("PAUSE");
    return 0;
}
unsigned __stdcall ThreadFun(void* par)
{
    for(int i=0;i<10;++i)
        cout<<"cout:"<<++count<<endl;
    count=0;
    //设置事件为激活状态,这里是执行线程
    cout<<"ReleaseMutex:"<<SetEvent(ThreadEvent)<<endl;
    return 0;
}

结果和预期的一样,就不上图了,SetEvent方法执行结果也是TRUE。

本文版权归kennyMc和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。