说明
这里说的事件指的是用API函数创建的事件而非CEvent类,其实都差不多。这里不加说明则指的都是WIN32 API
在实验过程中发现事件Event和互斥对象Mutex有3个很重要的区别:
区别1:
当线程2等待的是事件Event,并且是无限期等待时(INFINITE),如果拥有该事件Event的线程1退出时没有置事件信号为有信号,那么线程2会无限期等待下去而WaitForSingleObject不会返回。
而如果线程2等待的是互斥对象Mutex,并且是无限期等待,如果拥有该互斥对象Mutex的线程1退出时没有释放该互斥对象Mutex,那么操作系统会收回该对象给正在等待的线程2,WaitForSingleObject会返回WAIT_ABANDONED。
这也符合WaitForSingleObject函数对返回值WAIT_ABANDONED的说明:
对上述三个返回值来说,不管返回哪个值,函数总归是返回了,返回了就可以继续往下执行,只不过是按照返回值决定是不是要继续而已。
如果线程不是无限期等待,则函数WaitForSingleObject总会返回。只不过等待的对象不同,操作系统的处理不同而已。
区别2:
关于多次调用WaitForSingleObject方面的差别,如果线程等待的是事件Event,多次调用该函数的时候(默认为自动重置方式),每调用一次系统都会自动把该事件置为无信号,因此从第二次调用的时候开始就相当于再次等待该事件信号,而实际上该事件信号已经由系统自动置为了无信号,因此要么超时,要么无限期等待。
如果线程等待的是互斥对象Mutex,多次调用该函数的时候,每调用一次,对象内部的计数器加1,说明该线程多次拥有了该对象,该线程仍然拥有该互斥对象,在释放的时候就要释放多次。
区别3:
互斥对象Mutex是谁拥有谁释放,其他线程使用不管用。
事件Event可在任何线程中置为有信号/ 无信号。
1 创建事件
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性,可为NULL 默认安全属性
BOOL bManualRest, // 人工 / 自动重置,TRUE为人工, FALSE为自动
BOOL bInitialState, //初试是否有信号,TRUE为有信号, FALSE为无信号
LPCTSTR lpName //名字,可根据名字使内存中只有一个程序实例(根据GetLastError返回值为已存在同名事件)
);
eg.
HANDLE hEvent = CreatEvent(NULL, TRUE, FALSE, "test"); //默认安全属性,人工重置,初试无信号,名为test
说明:人工重置方式即当WaitForSingleObject函数等待该事件为有信号时需要显示地调用ResetEvent函数使该事件为无信号,如果有多个线程等待同一个事件,那么不能使用人工重置方式,因为这种方式重置的不及时,也就是说存在这样一种情况:多个线程同时等到有信号。
因此强烈建议使用自动重置!!!
2 重置事件信号(使其为无信号)
BOOL ResetEvent(
HANDLE hEvent; //事件句柄
);
eg.
ResetEvent(hEvent);
使其为无信号。
3 设置事件信号(使其为有信号)
BOOL SetEvent(
HANDLE hEvent; //事件句柄
);
eg.
SetEvent(hEvent);
使其为有信号。
4 实例
73EventDlg.h 类声明/
public:
static UINT MyThread1(LPVOID lpParam);
static UINT MyThread2(LPVOID lpParam);
private:
static HANDLE m_hEvent;
73EventDlg.cpp 类定义/
HANDLE CMy73EventDlg::m_hEvent = INVALID_HANDLE_VALUE;
BOOL CMy73EventDlg::OnInitDialog()
{
//codes
m_hEvent = CreateEvent(NULL, FALSE, FALSE, "testevent"); //自动重置,初试无信号
if (m_hEvent)
{
if (ERROR_ALREADY_EXISTS == GetLastError()) //内存中已存在名为testevent的事件
{
MessageBox("名为testevent的事件对象已经创建");
return FALSE;
}
}
SetEvent(m_hEvent); //设置为有信号
CWinThread* pThread1 = AfxBeginThread(MyThread1, (LPVOID)&m_ctrlEdit1);
CWinThread* pThread2 = AfxBeginThread(MyThread2, (LPVOID)&m_ctrlEdit2);
//codes
}
//线程1
UINT CMy73EventDlg::MyThread1(LPVOID lpParam)
{
CString str;
int a = 0;
CEdit* pEdit = (CEdit*)lpParam;
while (true)
{
WaitForSingleObject(m_hEvent, 1000);
WaitForSingleObject(m_hEvent, 1000); //请求多次,由于是自动重置,因此必定会返回超时
str.Format("%d", ++a);
str += " a";
pEdit->SetWindowText(str);
Sleep(500);
if (a >= 5)
{
break; //这里退出时没有设置为有信号,即该事件还是为无信号状态
}
SetEvent(m_hEvent); //设置为有信号
}
return 0;
}
//线程2
UINT CMy73EventDlg::MyThread2(LPVOID lpParam)
{
CString str;
int b = 0;
CEdit* pEdit = (CEdit*)lpParam;
while(true)
{
WaitForSingleObject(m_hEvent, 2000); //线程1中a>=5时线程1退出,线程1退出时没有把事件置为有信号,因此会永远在 //此等待超时,返回WAIT_TIMEOUT!!!
WaitForSingleObject(m_hEvent, INFINITE); //多次请求,由于是自动重置,因此这里永远不会等到有信号!函数一直停在此 //处!
str.Format("%d", ++b);
str += " b";
pEdit->SetWindowText(str);
Sleep(500);
SetEvent(m_hEvent);
}
return 0;
}
从这个例子中可以很好的知道事件的用法以及事件和互斥对象的区别。