互斥对象、事件对象、关键代码段的应用和比较

        线程同步的方式主要有三种:互斥对象、事件对象和关键代码段。

一.     互斥对象

        互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。

互斥对象包含一个使用数量,一个线程ID和一个计数器。其中线程ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

       创建互斥对象:调用函数CreateMutex。调用成功,该函数返回所创建的互斥对象的句柄。

       请求互斥对象的所有权:调用函数WaitForSingleObject函数。线程必须主动请求共享对象的所有权才能获得该所有权。

       释放指定互斥对象的所有权:调用ReleaseMutex函数。线程访问共享资源结束后,线程要主动释放对互斥对象的所有权,使该对象处于已通知状态。

 

二.     事件对象

        事件对象也属于内核对象,它包含以下三个成员:

        ●    使用计数;

        ●    用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值;

        ●    用于指明该事件处于已通知状态还是未通知状态的布尔值。

        事件对象有两种类型:人工重置的事件对象和自动重置的事件对象。这两种事件对象的区别在于当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程;而当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程。

1.      创建事件对象

       调用CreateEvent函数创建或打开一个命名的或匿名的事件对象。

2.      设置事件对象状态

       调用SetEvent函数把指定的事件对象设置为有信号状态。

3.      重置事件对象状态

       调用ResetEvent函数把指定的事件对象设置为无信号状态。

4.      请求事件对象

线程通过调用WaitForSingleObject函数请求事件对象。

三.     关键代码段

        关键代码段,也称为临界区,工作在用户方式下。它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。通常把多线程中访问同一种资源的那部分代码当做关键代码段。

1.      初始化关键代码段

       调用InitializeCriticalSection函数初始化一个关键代码段。

       该函数只有一个指向CRITICAL_SECTION结构体的指针。在调用InitializeCriticalSection函数之前,首先需要构造一个CRITICAL_SCTION结构体类型的对象,然后将该对象的地址传递给InitializeCriticalSection函数。

2.      进入关键代码段

       调用EnterCriticalSection函数,以获得指定的临界区对象的所有权,该函数等待指定的临界区对象的所有权,如果该所有权赋予了调用线程,则该函数就返回;否则该函数会一直等待,从而导致线程等待。

 

3.      退出关键代码段

        线程使用完临界区所保护的资源之后,需要调用LeaveCriticalSection函数,释放指定的临界区对象的所有权。之后,其他想要获得该临界区对象所有权的线程就可以获得该所有权,从而进入关键代码段,访问保护的资源。

4.      删除临界区

          当临界区不再需要时,可以调用DeleteCriticalSection函数释放该对象,该函数将释放一个没有被任何线程所拥有的临界区对象的所有资源。

四.     三者的比较

        对于上面介绍的三种线程同步的方式,它们之间的区别如下所述:

        ●    互斥对象和事件都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个线程中的各个线程间进行同步。

        ●    关键代码段工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值。

       通常,在编写多线程程序并需要实现线程同步时,首选关键代码段,由于它的使用比较简单,如果是在MFC程序中使用的话,可以在类的构造函数中调用InitializeCriticalSection函数,在该类的析构函数中调用DeleteCriticalSection函数,在所需保护的代码前面调用EnterCriticalSection函数,在访问完所需保护的资源后,调用LeaveCriticalSection函数。可见,关键代码段在使用上是非常方便的,但有几点需要注意:一是在程序中调用了EnterCriticalSection后,一定要相应的调用LeaveCriticalSection函数,否则其他等待该临界区对象所有权的线程将无法执行。二是如果访问关键代码段时,使用了多个临界区对象,就要注意防止线程死锁的发生。另外,如果需要在多个线程间的各个线程间实现同步的话,可以使用互斥对象和事件对象。

 

 

  前提:假设有个经理,下面有5个项目组.经理同时最多能接5个项目,每个项目组一次只能做一个项目。

信标Semaphore:信标相当于,5个项目组都争先恐后的争夺项目。如果经理有5个项目,那么5个项目组都可以做。如果经理有3个项目,那就有2个项目没事情做,在等待。如果经理没有项目,那么5个组,都在闲着。

事件Event:相当于,外面有个项目,经理把项目接回来了。站在门口大喊:有项目了。喊完了,经理就不管了这个项目又没有人处理。

互斥Mutex:相当于,经理手上有1个项目。下面的组,主动地争夺项目,谁抢到谁就有事情做,没抢到的就得待着。