当前位置:软件学习 > 其它软件 >>

秒杀多线程第六篇 经典线程同步,事件Event


阅读本篇之前推荐阅读以下姊妹篇:

《秒杀多线程第四篇 一个经典的多线程同步问题》http://www.zzzyk.com/kf/201204/126963.html

《秒杀多线程第五篇 经典线程同步关键段CS》http://www.zzzyk.com/kf/201204/126965.html

 

上一篇中使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权”特性所以关键段只能用于线程的互斥而不能用于同步。本篇介绍用事件Event来尝试解决这个线程同步问题。

首先介绍下如何使用事件。事件Event实际上是个内核对象,它的使用非常方便。下面列出一些常用的函数。

 

第一个 CreateEvent

函数功能:创建事件

函数原型:

HANDLECreateEvent(

 LPSECURITY_ATTRIBUTESlpEventAttributes,

 BOOLbManualReset,

 BOOLbInitialState,

 LPCTSTRlpName

);

函数说明:

第一个参数表示安全控制,一般直接传入NULL。

第二个参数确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。打个小小比方,手动置位事件相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。自动置位事件就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。

第三个参数表示事件的初始状态,传入TRUR表示已触发。

第四个参数表示事件的名称,传入NULL表示匿名事件。

 

第二个 OpenEvent

函数功能:根据名称获得一个事件句柄。

函数原型:

HANDLEOpenEvent(

 DWORDdwDesiredAccess,

 BOOLbInheritHandle,

 LPCTSTRlpName     //名称

);

函数说明:

第一个参数表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。

第二个参数表示事件句柄继承性,一般传入TRUE即可。

第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。

 

第三个SetEvent

函数功能:触发事件

函数原型:BOOLSetEvent(HANDLEhEvent);

函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。

 

第四个ResetEvent

函数功能:将事件设为末触发

函数原型:BOOLResetEvent(HANDLEhEvent);

 

最后一个事件的清理与销毁

由于事件是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。

 

在经典多线程问题中设置一个事件和一个关键段。用事件处理主线程与子线程的同步,用关键段来处理各子线程间的互斥。详见代码:

[cpp] #include <stdio.h>  
#include <process.h>  
#include <windows.h>  
long g_nNum; 
unsigned int __stdcall Fun(void *pPM); 
const int THREAD_NUM = 10; 
//事件与关键段  
HANDLE  g_hThreadEvent; 
CRITICAL_SECTION g_csThreadCode; 
int main() 

    printf("     经典线程同步 事件Event\n"); 
    printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n"); 
    //初始化事件和关键段 自动置位,初始无触发的匿名事件  
    g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);  
    InitializeCriticalSection(&g_csThreadCode); 
 
    HANDLE  handle[THREAD_NUM];  
    g_nNum = 0; 
    int i = 0; 
    while (i < THREAD_NUM)  
    { 
        handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL); 
        WaitForSingleObject(g_hThreadEvent, INFINITE); //等待事件被触发  
        i++; 
    } 
    WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE); 
 
    //销毁事件和关键段  
    CloseHandle(g_hThreadEvent); 
    DeleteCriticalSection(&g_csThreadCode); 
    return 0; 

unsigned int __stdcall Fun(void *pPM) 

    int nThreadNum = *(int *)pPM;  
    SetEvent(g_hThreadEvent); //触发事件  
     
    Sleep(50);//some work should to do  
     
    EnterCriticalSection(&g_csThreadCode); 
    g_nNum++; 
    Sleep(0);//some work should to do  
    printf("线程编号为%d  全局资源值为%d\n", nThreadNum, g_nNum);  
    LeaveCriticalSection(&g_csThreadCode); 
    return 0; 

#include <stdio.h>
#include <process.h>
#include <windows.h>
long g_nNum;
unsigned int __stdcall Fun(void *pPM);
const int THREAD_NUM = 10;
//事件与关键段
HANDLE  g_hThreadEvent;
CRITICAL_SECTION g_csThreadCode;
int main()
{
 printf("     经典线程同步 事件Event\n");
 printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
 //初始化事件和关键段 自动置位,初始无触发的匿名事件
 g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
 InitializeCriticalSection(&g_csThreadCode);

 HANDLE  handle[THREAD_NUM]; 
 g_nNum = 0;
 int i = 0;
 while (i < THREAD_NUM)
 {
  handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
  WaitForSingleObject(g_hThreadEvent, INFINITE); //等待事件被触发
  i++;
 }
 WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);

 //销毁事件和关键段
 CloseHandle(g_hThreadEvent);
 DeleteCriticalSection(&g_csThreadCode);
 return 0;
}
unsigned int __stdcall Fun(void *pPM)
{
 int nThreadNum = *(int *)pPM;
 SetEvent(g_hThreadEvent); //触发事件
 
 Sleep(50);//some work should to do
 
 EnterCriticalSection(&g_csThreadCode);
 g_nNum++;
 Sleep(0);//some work should to do
 printf("线程编号为%d  全局资源值为%d\n", nThreadNum, g_nNum);
 LeaveCriticalSection(&g_csThreadCode);
 return 0;
}
运行结果如下图:

 \


 

可以看出来,经典线线程同步问题已经圆满的解决了——线程编号的输出没有重复,说明主线程与子线程达到了同步。全局资源的输出是递增的,说明各子线程已经互斥的访问和输出该全局资源。

 

现在我们知道了如何使用事件,但学习就应该要深入的学习,何况微软给事件还提供了PulseEvent()函数,所以接下来再继续深挖下事件Event,看看它还有什么秘密没。

先来看看这个函数的原形:

第五个PulseEvent

函数功能:将事件触发后立即将事件设置为未触发,相当于触发一个事件脉冲。

函数原型:BOOLPulseEvent(HANDLEhEvent);

函数说明:这是一个不常用的事件函数,此函数相当于SetEvent()后立即调用ResetEvent();此时情况可以分为两种:

1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。

2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。

此后事件是末触发的。该函数不稳定,因为无法预知在调用PulseEvent ()时哪些线程正处于等待状态。

 

     

补充:软件开发 , 其他 ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,