第一节:【Window】创建线程的3种方式
第二节:【Window】线程同步概述
第三节:【Window】线程同步方式1——临界区(关键代码段)
第四节:【Window】线程同步方式2——互斥量
第五节:【Window】线程同步方式3——事件
第六节:【Window】线程同步方式4——信号量
文章目录
1 事件
事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步
操作。
事件(Event)是WIN32提供的最灵活的线程间同步方式,事件可以处于激发状态(signaled or true)或未激发状态(unsignal or false)。根据状态变迁方式的不同,事件可分为两类:
- 手动设置:这种对象只可能用程序手动设置,在需要该事件或者事件发生时,采用
SetEvent
及ResetEvent
来进行设置。 - 自动恢复:一旦事件发生并被处理后,自动恢复到没有事件状态,不需要再次设置。
使用”事件”机制应注意以下事项:
- 如果跨进程访问事件,必须对事件命名,在对事件命名的时候,要注意不要与系统命名空间中的其它全局命名对象冲突;
- 事件是否要自动恢复;
- 事件的初始状态设置。
2 信号量包含的几个操作原语
1)CreateEvent
-
函数功能:创建事件
-
函数原型:
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName );
-
函数说明:
lpEventAttributes 表示安全控制,一般直接传入NUL bManualReset 确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。 InitialState 表示事件的初始状态,传入TRUR表示已触发。 pName 表示事件的名称,传入NULL表示匿名事件。
2)OpenEvent
-
函数功能:根据名称获得一个事件句柄,打开事件。
-
函数原型:
HANDLE OpenEvent( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName //名称 );
-
函数说明:
dwDesiredAccess 表示访问权限,对事件一般传入 EVENT_ALL_ACCESS
。详细解释可以查看MSDN文档。InheritHandle 表示事件句柄继承性,一般传入TRUE即可。 pName 表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。
3)SetEvent
- 函数功能:设置事件的状态为有标记,释放任意等待线程。如果事件是手工的,此事件将保持有标记直到调用ResetEvent,这种情况下将释放多个线程;如果事件是自动的,此事件将保持有标记,直到一个线程被释放,系统将设置事件的状态为无标记;如果没有线程在等待,则此事件将保持有标记,直到一个线程被释放。
- 函数原型:
BOOL SetEvent(HANDLE hEvent);
- 函数说明:
每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。 - 返回值:如果操作成功,则返回非零值,否则为0。
4)ResetEvent
- 函数功能:将事件设为末触发
- 函数原型:
BOOL ResetEvent(HANDLE hEvent);
5)WaitForSingleObject
-
函数功能:WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。
-
函数原型:
等待一个事件 : DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds); 等待多个事件 : DWORD WaitForMultipleObjects( DWORD nCount, // 等待句柄数 CONST HANDLE *lpHandles, //指向句柄数组 BOOL bWaitAll, //是否完全等待标志 DWORD dwMilliseconds //等待时间 )
-
函数说明:
hHandle 对象句柄。可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等 wMilliseconds 定时时间间隔,单位为milliseconds(毫秒).如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回。 -
返回值
执行成功,返回值指示出引发函数返回的事件。它可能为以下值:WAIT_ABANDONED 当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值。 WAIT_OBJECT_0 指定的对象出有有信号状态 IT_TIMEOUT 等待超时 IT_FAILED 出现错误,可通过GetLastError得到错误代
6)事件的清理与销毁
由于事件是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。
3 事件说明
- 首先我们需要创建CreateEvent一个事件对象,它的使用方式是触发方式;
- 要想被WaitForSingleObject等待到该事件对象必须是有信号的;
- 事件要想有信号可以用SetEvent手动置为有信号;
- 要想事件对象无信号可以使用ResetEvent(或者在创建事件对象时就声明该事件对象,WaitForSingleObject自动置为无信号,见上面CreateEvent第二个参数);
打个小小比方,
手动置位事件
相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。
自动置位事件
就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。当然事件对象的句柄是要关闭的CloseHandle。
4. 举例
事件对象模拟火车售票的过程:
(利用通知的方式对共同资源的互斥控制)
// EventThread.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "windows.h"
using namespace std;
DWORD WINAPI FunProc1(LPVOID lpParameter);
DWORD WINAPI FunProc2(LPVOID lpParameter);
int ticket = 27;
HANDLE g_hEvent; //定义事件句柄
void main()
{
HANDLE hThread1;//线程句柄1
HANDLE hThread2;//线程句柄2
g_hEvent = CreateEvent(
NULL,
FALSE, //传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。
TRUE, // 表示事件的初始状态,传入TRUR表示已触发。
NULL //表示事件的名称,传入NULL表示匿名事件。
);//创建事件对象
//线程1先被激活
hThread1 = CreateThread(
NULL, //为NULL则表示返回的句柄不能被子进程继承
0, //设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。
FunProc1, //指向线程函数的指针
NULL, //向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
0, //控制线程创建的标志,0:表示创建后立即激活
NULL //保存新线程的id,是指向线程id的指针,如果为空,线程id不被返回
); //函数成功,返回线程句柄,否则返回NULL
hThread2 = CreateThread(NULL, 0, FunProc2, NULL, 0, NULL);
if (hThread1 != NULL)
CloseHandle(hThread1);
if (hThread2 != NULL)
CloseHandle(hThread2);
Sleep(1000); // 让主线程睡眠1秒
if (g_hEvent != NULL)
CloseHandle(g_hEvent);
}
DWORD WINAPI FunProc1(LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(g_hEvent, INFINITE); //阻塞状态:一直等待对象为有信号状态,申请事件g_hEvent; INFINITE:对象被触发信号后,函数才会返回
//ResetEvent(g_hEvent); //重置为无信号(申请钥匙,得到钥匙)
if (ticket > 0) //如果当前票数大于0,线程1使票数减一,
{
Sleep(1);
cout << "ticket 1:" << ticket-- << endl;
SetEvent(g_hEvent);//设置为有信号(放弃钥匙,不再拥有)
}
else
{
SetEvent(g_hEvent);//设置为有信号(放弃钥匙,不再拥有)
break;
}
}
return 0;
}
DWORD WINAPI FunProc2(LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(g_hEvent, INFINITE);
if (ticket > 0)
{
Sleep(1);
cout << "ticket 2:" << ticket-- << endl;
SetEvent(g_hEvent); //设置为有信号
}
else
{
SetEvent(g_hEvent); //设置为有信号
break;
}
}
return 0;
}
同步过程分析:
-
g_hEvent=CreateEvent(NULL,FALSE,TRUE,NULL)
的意义: 自动重置事件,初始时该事件对象就有信号。 -
执行顺序:
① 开始的时候事件对象具有信号,当第一个线程hThread1
申请获得事件对象后,进入if语句,线程hThread1
会暂停1毫秒;
② 于是第二线程hThread2
运行,因为此时g_hEvent已经无信号,故无法申请,所以无法执行下面的程序;
③ 此时第一个线程hThread1
睡醒,开始执行自己的任务,然后设置对象为有信号(可以被其他线程申请);
④ 于是第二个线程hThread2
申请得到事件对象,进入if语句,线程hThread2
会暂停1毫秒;
⑤ 第一线程hThread1
运行,因为此时g_hEvent已经无信号,故无法申请,所以无法执行下面的程序。
⑥ 此时第二个线程hThread2
睡醒,开始执行自己的任务,然后设置对象为有信号(可以被其他线程申请);…与此往复,直到退出循环。
#include "windows.h"
#include "iostream"
using namespace std;
DWORD WINAPI FunProc1(LPVOID lpParameter);
DWORD WINAPI FunProc2(LPVOID lpParameter);
int ticket = 27;
HANDLE g_hEvent1, g_hEvent2;
void main()
{
HANDLE hThread1;
HANDLE hThread2;
g_hEvent1 = CreateEvent(NULL, FALSE, TRUE, NULL); //创建事件对象1,自动重置事件,已触发
g_hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL); //创建事件对象2,自动重置事件,没触发
hThread1 = CreateThread(NULL, 0, FunProc1, NULL, 0, NULL);//创建后立即激活
hThread2 = CreateThread(NULL, 0, FunProc2, NULL, 0, NULL);//创建后立即激活
if (hThread1 != NULL)CloseHandle(hThread1);
if (hThread2 != NULL)CloseHandle(hThread2);
Sleep(1000); // 让主线程睡眠1秒
if (g_hEvent1 != NULL)CloseHandle(g_hEvent1);
if (g_hEvent2 != NULL)CloseHandle(g_hEvent2);
}
DWORD WINAPI FunProc1(LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(g_hEvent1, INFINITE);//申请有信号的事件对象g_hEvent1
//ResetEvent(g_hEvent); //重置为无信号(申请钥匙,得带钥匙)
if (ticket > 0)
{
Sleep(1);
cout << "ticket 1:" << ticket-- << endl;
SetEvent(g_hEvent2);//设置为有信号,初始状态为无信号;让线程2执行
}
else
{
SetEvent(g_hEvent2);//设置为有信号,让线程2执行
break;
}
}
return 0;
}
DWORD WINAPI FunProc2(LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(g_hEvent2, INFINITE); //申请有信号的事件对象g_hEvent2
if (ticket > 0)
{
Sleep(1);
cout << "ticket 2:" << ticket-- << endl;
SetEvent(g_hEvent1); //让线程1执行
}
else
{
SetEvent(g_hEvent1);
break;
}
}
return 0;
}
- 执行顺序:
① 开始的时候事件对象g_hEvent1
具有信号、g_hEvent2
无信号,当第一个线程hThread1
申请获得事件对象后,进入if语句,线程hThread1
会暂停1毫秒;
② 于是第二线程hThread2
运行,因为此时g_hEvent2
无信号,故无法申请,无法执行;
③ 此时第一个线程hThread1
睡醒,开始执行自己的任务,然后设置对象g_hEvent2
为有信号(可以被其他线程申请);
④ 于是第二个线程hThread2
申请得到事件对象g_hEvent2
,进入if语句,线程hThread2
会暂停1毫秒;
⑤ 第一线程hThread1
运行,此时g_hEvent1
有信号,进入if语句。
…与此往复,直到退出循环。
#include <Windows.h>
#include <iostream>
using namespace std;
typedef struct _STRUCT_DATA_
{
int id; //用于标识出票id
int tickets;
}_DATA, * _pDATA;
HANDLE g_hEvent;
DWORD WINAPI Fun1(LPVOID lpParam);
DWORD WINAPI Fun2(LPVOID lpParam);
void main()
{
HANDLE hThread1;
HANDLE hThread2;
_DATA stru_data;
stru_data.id = 0;
stru_data.tickets = 20;
g_hEvent = CreateEvent(NULL, FALSE, FALSE, L"Ticket");
if (g_hEvent) {
if (ERROR_ALREADY_EXISTS == GetLastError()) {
cout << "the instance is exist!" << endl;
return;
}
}
hThread1 = CreateThread(NULL, 0, Fun1, &stru_data, 0, NULL);
hThread2 = CreateThread(NULL, 0, Fun2, &stru_data, 0, NULL);
if (hThread1)
CloseHandle(hThread1);
if (hThread2)
CloseHandle(hThread2);
SetEvent(g_hEvent);
Sleep(4000);
if (g_hEvent) CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1(LPVOID lpParam)
{
_pDATA data = (_pDATA)lpParam;
while (TRUE)
{
WaitForSingleObject(g_hEvent, INFINITE);
if (data->tickets > 0)
{
Sleep(1);
cout << "fun1: " << data->id++ ;
cout << " thread 1: sell ticket: " << data->tickets-- << endl;
SetEvent(g_hEvent);
}
else {
SetEvent(g_hEvent);
break;
}
}
return 0;
}
DWORD WINAPI Fun2(LPVOID lpParam)
{
_pDATA data = (_pDATA)lpParam;
while (TRUE)
{
WaitForSingleObject(g_hEvent, INFINITE);
if (data->tickets > 0)
{
Sleep(1);
cout << "fun2: " << data->id++;
cout << " thread 2: sell ticket: " << data->tickets-- << endl;
SetEvent(g_hEvent);
}
else {
SetEvent(g_hEvent);
break;
}
}
return 0;
}