多线程---event

来源:互联网 发布:淘宝自动回复设置内容 编辑:程序博客网 时间:2024/05/16 12:26

转自:http://blog.sina.com.cn/s/blog_52324e0b0100oitj.html

 EVENT是多线程同步机制中最具弹性。它的激发与未激发状态完全是由程序来进行控制的。问不会随着Wait..函数而改变。它是一个内核对象。

1.创建event:

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,   //安全属性
  BOOL bManualReset,    //是否设置为手动重置的对象,TRUE 手动重置, FALSE 自动重置
  BOOL bInitialState,   //初始是否设置为激发状态
  LPTSTR lpName         //名称
); (更多,参考MSDN)

根据这个创建的函数,event可以分为两种,手动重置对象和自动重置对象。

1.手动重置对象:对象的激发与未激发状态都需要程序中调用相应的函数进行设置。wait..函数并不能改变对象的状态。设置对象未激发状态可以调用setevent和pulseevent,设置对象为未激发状态可以调用resetevent.当手动重置事件变为激发状态,那么等待该对象的所有线程都将变成可调度线程。下面是一个手动重置对象的列子:

#include <iostream>
#include <cstdlib>
using namespace std;
#include <Windows.h>

//线程函数
DWORD WINAPI ThreadProc(LPVOID param);

//全局事件对象
HANDLE gEvent;

int main()
{
 //创建手动重置对象,初始为未激发状态
 gEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
 if (gEvent == NULL)
 {
  cout<<"Create event failed"<<endl;
  return 0;
 }

 //创建三个线程
 HANDLE threadHandles[3];
 DWORD threadId;
 for (int i = 0; i < 3; ++i)
 {
  threadHandles[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)i, 0, &threadId);
  if (threadHandles[i] == NULL)
  {
   cout<<"Create thread failed."<<endl;
   return 0;
  }
 }

 SetEvent(gEvent);

 WaitForMultipleObjects(3, threadHandles, TRUE, INFINITE);
 for (int i = 0; i < 3; ++i)
 {
  CloseHandle(threadHandles[i]);
 }
 CloseHandle(gEvent);

 system("pause");
 return 0;
}

DWORD WINAPI ThreadProc(LPVOID param)
{
 cout<<"Thread "<<(int)param<<" wait for event."<<endl;
 WaitForSingleObject(gEvent, INFINITE);
 ResetEvent(gEvent);
 cout<<"Thread "<<(int)param<<" worked."<<endl;
 SetEvent(gEvent);
 return 0;
}

注意:对于手动重置对象,由于其状态是由程序来进行控制的。所以调用wait..函数不能改变对象的状态。所以如果在WaitForSingleObject(gEvent, INFINITE);ResetEvent(gEvent);发生上下文切换,那么多个线程之间的操作可能就会出现混乱。

2.自动重置对象:当一个线程等待对象成功以后,会自动的将这个对象设置为未激发状态。这样就可以避免上下文切换引起的多个线程可以同时进入事件对象保护的代码中。一个自动重置事件对象的例子:

//线程函数
DWORD WINAPI ThreadProc(LPVOID param);

//全局事件对象
HANDLE gEvent;

int main()
{
 //创建手动重置对象,初始为未激发状态
 gEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
 if (gEvent == NULL)
 {
  cout<<"Create event failed"<<endl;
  return 0;
 }

 //创建三个线程
 HANDLE threadHandles[3];
 DWORD threadId;
 for (int i = 0; i < 3; ++i)
 {
  threadHandles[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)i, 0, &threadId);
  if (threadHandles[i] == NULL)
  {
   cout<<"Create thread failed."<<endl;
   return 0;
  }
 }

 SetEvent(gEvent);

 WaitForMultipleObjects(3, threadHandles, TRUE, INFINITE);
 for (int i = 0; i < 3; ++i)
 {
  CloseHandle(threadHandles[i]);
 }
 CloseHandle(gEvent);

 system("pause");
 return 0;
}

DWORD WINAPI ThreadProc(LPVOID param)
{
 cout<<"Thread "<<(int)param<<" wait for event."<<endl;
 WaitForSingleObject(gEvent, INFINITE);
 cout<<"Thread "<<(int)param<<" worked."<<endl;
 SetEvent(gEvent);
 return 0;
}

 

几点比较杂的东西:
关于PulseEvent,WIN32多线程程序设计中关于它的说明:

如果是一个手动重置对象,它会把对象设置为激发状态,唤醒"所有"等待中的线程,然后把event恢复为非激发状态。如果是一个自动重置对象,它会把对象设为激发状态,唤醒"一个"等待中的线程,然后把对象设置为未激发状态。
由于存在状态切换,如果当这种状态切换的时候没有任何的线程等待该对象的时候(可能是真的没有线程等待,或者是上下文切换),那么这个要求线程苏醒变成可调度的请求就会失败。

windows核心编程中关于PulseEvent的说明:

PulseEvent函数使得事件变为已通知状态,然后立即又变为未通知状态,这就像在调用setevent后又立即调用resetevent函数一样。如果在人工重置的事件上调用PulseEvent函数,那么在发出该事件时,等待该事件的任何一个线程或所有线程将变为可调度线程。如果在自动重置事件上调用PulseEvent函数,那么只有一个等待该事件的线程变为可调度线程。如果在发出事件时没有任何线程在等待该事件,那么将不起任何作用。
PulseEvent函数并不非常有用。实际上我在自己的应用程序中从未使用它,因为根本不知道什么线程将会看到事件的发出并变成可调度线程。由于在调用PulseEvent时无法知道任何线程的状态,因此该函数并不那么有用。我相信在有些情况下,虽然PulseEvent函数可以方便地供你使用,但是你根本想不起要去使用它。

 

最后关于手动重置以及自动重置的几点说明:
手动重置,可以当做一个开关。当你进行完必要的初始化之后,就可以打开这个开关,然后让所有的处理的线程进行处理(当然进行应该的应该都是只读的处理)。自动重置对象可以像互斥对象一样进行排他性的保护,但是比起互斥对象它更加的灵活。

原创粉丝点击