第9章 用内核对象进行线程同步(1)_事件对象(Event)
9.1 等待函数
(1)WaitForSingleObject(hObject,dwMilliseonds);
①dwMilliseconds为INFINITE时表示无限等待
②dwMilliseconds=0时表示立即返回,即使它要等待的条件还没满足
③dwMilliseconds为其它值时(单位为ms),其返回值有三种情况:A、WAIT_OBJECT_0表示等待的对象触发。WAIT_TIMEOUT表示超时。WAIT_FAILED:表示可能传入了无效句柄,可进一步调用GetLastError来得到更详细的信息。
(2)WaitForMultipleObjects函数
参数 |
描述 |
nCount |
要等待的内核对象的数量,这个值必须在1~MAXIMUM_WAIT_OBJECTS之间(即最多64个) |
lpHandles |
指向等待的内核对象的数组 |
bWaitAll |
TRUE时表示等待的所有内核对象都触发;FALSE:只要有一个触发就返回 |
dwMilliseconds |
与WaitForSingleObject的含义相同 |
返回值 |
WAIT_FAILED:传入了无效句柄。 WAIT_TIMEOUT:超时 WAIT_OBJECT_0~WAIT_OBJECT_x:如果bWaitAll为TRUE时,则当所有对象被触发时返回WAIT_OBJECT_0;如果bWaitAll为FALSE,则只要有一个对象被触发,函数就返回WAIT_OBJECT_x,其中的x表示上述被触发对象在lpHandles数组中的下标,即用于通知是哪个对象被触发。 |
9.2 等待成功所引起的副作用
(1)WaitForSingleObject或WaitForMultipleObjects函数在调用成功后,如果对象的状态也被改变了,这称为“等待成功所引起的副作用”(注意调用失败并不会改变对象状态)。如事件对象(Event),当为“自动设置”时,在调用WaitForSingleObject成功以后会被自动恢复初始的为“无信号”状态,这就是副作用。当然如果设置为“手动”,则不会有这种副作用。此外,其他对象有的也有副作用,但有的完全没有。
(2)举例说明等待成功的副作用(设两个线程以完全相同的方式调用WaitForMultiple*函数)
HANDLE hEvent[2];
hEvent[0] = hAutoResetEvent1; //初始化时为“无信号”状态
hEvent[1] = hAutoResetEvent2; //初始化时为“无信号”状态
WaitForMultipleObjects(2,hEvent,TRUE,INFINITE);
①刚调用WaitFor*函数时,两个事件对象均未被触发,所以两个线程都进入等待状态。
②然后当hAutoResetEvent1被触发,但由于hAutoResetEvent2未被触发,所以系统不会唤醒任何一个线程,所以WaitFor*函数没有成功返回,不会对hAutoResetEvent1对象产生副作用。
③接下来hAutoResetEvent2被触发,此时两个线程中的一个检测到,并等待成功。这时该线程的WaitFor*函数会以原子方式将两个事件对象重设为“无信号”状态(即在改变两个事件状态的过程不允许被打断,此时其它线程无法访问和改变这两个事件对象,这种原子方式的特征是为了防止死锁的发生,见课本P236页)。但对于另一个线程来说,虽然他曾经检测到hAutoResetEvent1被触发,但现在看到的状态却是未触发,所以它会继续等待,直到这两个对象都被触发。这就是副作用,因为hAutoResetEvent曾经被触发过,而现在还要重新等待。
9.3 事件内核对象
(1)创建事件内核对象:CreateEvent函数
参数 |
描述 |
psa |
安全属性(如使用计数、句柄继承等) |
bManualReset |
TRUE:手动重置事件,两个特点:①当事件被触发时,正在等待该事件的所有线程都变为可调度;②调用WaitFor*函数后,不会自动重置对象为“无信号”状态。 FALSE:自动重置事件,两个特点:①当事件被触发时,只有一个正在等待该事件的线程变为可调度;②调用WaitFor*函数会,会自动将事件的状态重置为“无信号” |
bInitialState |
创建时的初始状态,TRUE为有信号,FALSE为无信号。 |
pszName |
对象的名字 |
返回值 |
返回与当前进程相关的事件内核对象句柄 |
(2)CreateEventEx函数
参数 |
描述 |
psa |
安全属性(如使用计数、句柄继承等) |
pszName |
对象的名字 |
dwFlags |
CREATE_EVENT_INITIAL_SET(0x02):初始化为触发状态 CREATE_EVENT_MANUAL_RESET(0x01):手动重置事件 |
dwDesiredAccess |
返回句柄的访问权限 |
返回值 |
返回与当前进程相关的事件内核对象句柄 |
(3)SetEvent——将事件设为触发状态
(4)ResetEvent——重置事件为未触发状态
★对于自动重置事件:当线程成功等到事件对象时,会自动重置为未触发状态,而不必调用ResetEvent函数
(5)PulseEvent函数——脉冲事件函数
①先触发事件然后立刻恢复到未触发状态,相当于调用SetEvent后立即调用ResetEvent。
②如果是手动重置事件调用PulseEvent,则会将所有正在等待事件的线程变成可调度状态。如果是自动重置事件,那么只有一个正在等待该事件的线程会变成可调度状态。
③如果当事件被脉冲触发的时候没有线程正在等待该事件,则不会产生任何效果。
(6)手动重置事件与自动重置事件的比较
【伪代码分析】
#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <tchar.h> typedef unsigned(__stdcall *PTHREAD_START) (void *); DWORD WINAPI WordCount(PVOID pvParam);
DWORD WINAPI SpellCheck(PVOID pvParam);
DWORD WINAPI GrammarCheck(PVOID pvParam); HANDLE g_hEvent; int _tmain()
{
//创建一个手动,未触发的事件
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //创建3个线程
HANDLE hThread[];
DWORD dwThreadID;
hThread[] = (HANDLE)_beginthreadex(NULL, , (PTHREAD_START)WordCount,NULL, ,(unsigned *)&dwThreadID);
hThread[] = (HANDLE)_beginthreadex(NULL, , (PTHREAD_START)SpellCheck,NULL, , (unsigned *)&dwThreadID);
hThread[] = (HANDLE)_beginthreadex(NULL, , (PTHREAD_START)GrammarCheck,NULL, , (unsigned *)&dwThreadID); //打开文件并读入内存
OpenFileAndReadContentsIntoMemory(...); //因为是手动重置事件,所以会同时唤醒3个线程
//假设上面的Open*函数己经把数据读到内存中,则当3个线程被唤醒
//时则会时候对该文件进行“字数统计”、“拼写检查”和“语法检查”
//因为这3个操作都是读操作,所以可以同时进程。
SetEvent(g_hEvent);
...
return ;
} //“字数统计”
DWORD WINAPI WordCount(PVOID pvParam)
{
//等待文件的数据被全部载入内存
WaitForSingleObject(g_hEvent, INFINITE); //访问内存块,因3个线程同时访问内存,所以这里只能是共享方式访问
...
return ;
} //“拼写检查”
DWORD WINAPI SpellCheck(PVOID pvParam)
{
//等待文件的数据被全部载入内存
WaitForSingleObject(g_hEvent, INFINITE); //访问内存块,因3个线程同时访问内存,所以这里只能是共享方式访问
...
return ;
} //“语法检查”
DWORD WINAPI GrammarCheck(PVOID pvParam)
{
//等待文件的数据被全部载入内存
WaitForSingleObject(g_hEvent, INFINITE); //访问内存块,因3个线程同时访问内存,所以这里只能是共享方式
...
return ;
} //如果将以上的事件对象改为自动重置时,即
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //此时的只要唤醒其中一个线程
SetEvent(g_hEvent); //此时的各线程函数会改为如果的形式,这时只有一个线程被唤醒
DWORD WINAPI WordCount\SpellCheck\GrammarCheck (PVOID pvParam)
{
//等待文件的数据被全部载入内存
WaitForSingleObject(g_hEvent, INFINITE); //因为是自动对象,该函数调用后会自动被设为未触发 //访问内存块,因为此时只唤醒一个线程,所以对内存的访问可以是可读可写的
...
SetEvent(g_hEvent); //唤醒剩下的2个线程中的一个,这句是与手动事件不同的,因为手动会全部唤醒,无需这句
return ;
}
【HandShake示例程序】——演示事件内核对象的使用,用于通知某项任务是否完成
/************************************************************************
Module:Handshake.cpp
Notices:Copyright(c)2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/
#include "../../CommonFiles/CmnHdr.h"
#include <tchar.h>
#include "resource.h" //////////////////////////////////////////////////////////////////////////
//当客户端发出一个请求后,事件触发
HANDLE g_hevtRequestSubmitted; //当服务端发出一个结果给客户端时,事件触发
HANDLE g_hevtResultReturned; //客户线程与服务线程共享的缓冲区
TCHAR g_szSharedRequestAndResultBuffer[]; //客户端发送一个特殊的字符串,用来结束程序
TCHAR g_szServerShutdown[] = TEXT("Server Shutdown"); //主对话框句柄。当服务线程接收到关闭消息时,会检测该句柄是否有效
HWND g_hMainDlg; //////////////////////////////////////////////////////////////////////////
DWORD WINAPI ServerThread(PVOID pvParam)
{
//假设服务线程没被停止
BOOL fShutdown = FALSE; while (!fShutdown){ //等待客户线程提交请求
WaitForSingleObject(g_hevtRequestSubmitted, INFINITE); //检查是否要结束程序(并关闭对话框时,会设置退出字符串)
//这里检查g_hMainDlg==NULL,可以防止用户手动输入“Server Shutdown”字符串
//而导致该线程退出的现象,也就是用户输入该字符串并不退出。
fShutdown = (g_hMainDlg == NULL) &&
(_tcscmp(g_szSharedRequestAndResultBuffer, g_szServerShutdown) == ); if (!fShutdown){
//处理客户线程的请求
_tcsrev(g_szSharedRequestAndResultBuffer); //反转字符串
} //通知客户线程,服务线程己处理完毕
SetEvent(g_hevtResultReturned);
} //客户线程要求退出
return ;
} //////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
chSETDLGICONS(hwnd, IDI_HANDSHAKE); //初始化编辑框
Edit_SetText(GetDlgItem(hwnd, IDC_REQUEST), TEXT("Some test data")); g_hMainDlg = hwnd;
return TRUE;
} void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotify)
{
switch (id)
{
case IDCANCEL:
EndDialog(hwnd, id);
break;
case IDC_SUBMIT: //将客户请求的字符串拷入共享缓冲区
Edit_GetText(GetDlgItem(hwnd, IDC_REQUEST),
g_szSharedRequestAndResultBuffer,_countof(g_szSharedRequestAndResultBuffer));
//客户线程提交请求,表示缓冲区己准备好。并等待服务线程返回处理结果
SignalObjectAndWait(g_hevtRequestSubmitted,
g_hevtResultReturned, INFINITE,FALSE); //显示服务线程的处理结果
Edit_SetText(GetDlgItem(hwnd, IDC_RESULT), g_szSharedRequestAndResultBuffer);
break;
}
} INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
}
return FALSE;
} int WINAPI _tWinMain(HINSTANCE hInstExe,HINSTANCE,PTSTR,int)
{
//创建并初始化2个未触发的自动事件对象
g_hevtRequestSubmitted = CreateEvent(NULL, FALSE, FALSE, NULL);
g_hevtResultReturned = CreateEvent(NULL, FALSE, FALSE, NULL); //创建服务线程
DWORD dwThreadID;
HANDLE hThreadServer = chBEGINTHREADEX(NULL, , ServerThread, NULL, , &dwThreadID); //运行UI线程(主线程,也是客户线程)
DialogBox(hInstExe, MAKEINTRESOURCE(IDD_HANDSHAKE), NULL, Dlg_Proc);
g_hMainDlg = NULL; //表示对话框己退出 //主线程UI线程正在关闭,这时服务线程可能被阻塞在Wait*函数中,将其唤醒,
//并填写退出字符串,以便退出
_tcscpy_s(g_szSharedRequestAndResultBuffer,
_countof(g_szSharedRequestAndResultBuffer),g_szServerShutdown);
SetEvent(g_hevtRequestSubmitted); //等待服务线程退出
HANDLE h[];
h[] = g_hevtResultReturned;
h[] = hThreadServer;
WaitForMultipleObjects(, h, TRUE, INFINITE); //关闭所有的句柄
CloseHandle(g_hevtResultReturned);
CloseHandle(g_hevtRequestSubmitted);
CloseHandle(hThreadServer);
return ;
}
//resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 9_HandShake.rc 使用
//
#define IDD_HANDSHAKE 101
#define IDI_HANDSHAKE 102
#define IDC_REQUEST 1002
#define IDC_RESULT 1003
#define IDC_SUBMIT 1004 // Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 103
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1003
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
//Handshake.rc
// Microsoft Visual C++ generated resource script.
//
#include "resource.h" #define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h" /////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS /////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED #ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
// TEXTINCLUDE
BEGIN
"resource.h\0"
END TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END #endif // APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////////
//
// Dialog
// IDD_HANDSHAKE DIALOGEX , , ,
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU
CAPTION "Handshake"
FONT , "MS Shell Dlg", , , 0x1
BEGIN
DEFPUSHBUTTON "提交到服务端",IDC_SUBMIT,,,,
GROUPBOX "客户端",IDC_STATIC,,,,
LTEXT "请求:",IDC_STATIC,,,,
LTEXT "结果:",IDC_STATIC,,,,
EDITTEXT IDC_REQUEST,,,,,ES_AUTOHSCROLL
EDITTEXT IDC_RESULT,,,,,ES_AUTOHSCROLL | ES_READONLY
END /////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
// #ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_HANDSHAKE, DIALOG
BEGIN
END
END
#endif // APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////////
//
// Icon
// // Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_HANDSHAKE ICON "Handshake.ico"
#endif // 中文(简体,中国) resources
///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
// /////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
【Event程序】模拟共享区的读取
#include <windows.h>
#include <tchar.h>
#include <locale.h> //////////////////////////////////////////////////////////////////////////
#define THREADCNT 6
HANDLE g_hWriteEvent = NULL;
HANDLE g_hThreads[THREADCNT] = { NULL }; //////////////////////////////////////////////////////////////////////////
DWORD WINAPI ThreadProc(PVOID pvParam);
void CreateEventsAndThreads(void);
void WriteToBuffer(void);
void CloseEvents(); int _tmain()
{
_tsetlocale(LC_ALL, _T("chs"));
CreateEventsAndThreads();
WriteToBuffer(); //将数据写入缓冲区。写完后,通过SetEvent通知各线程去读取
_tprintf(_T("\n")); DWORD dwWaitResult;
dwWaitResult = WaitForMultipleObjects(THREADCNT, g_hThreads, TRUE, INFINITE);
switch (dwWaitResult)
{
case WAIT_OBJECT_0: //因为bWaitAll为TRUE,所以当全部对象被触发,会返回Wait_object_0;
_tprintf(_T("\n子线程全部结束!\n"));
break;
default:
_tprintf(_T("\nWaitForMulipleObjects错误(%d)\n"), GetLastError());
break; } CloseEvents();
_tsystem(_T("PAUSE"));
return ;
} DWORD WINAPI ThreadProc(PVOID pvParam)
{
DWORD dwWaitResult = ;
_tprintf(_T("线程[%d]正在等待“写事件”被触发\n"),GetCurrentThreadId());
dwWaitResult = WaitForSingleObject(g_hWriteEvent, INFINITE); switch (dwWaitResult)
{
case WAIT_OBJECT_0:
_tprintf(_T("线程[%d]正在读取缓冲区中的数据...\n"), GetCurrentThreadId());
break; default:
_tprintf(_T("等待错误(%d)\n"), GetLastError());
break;
}
_tprintf(_T("线程[%d]退出!\n"), GetCurrentThreadId());
return ;
} void CreateEventsAndThreads(void)
{
//创建手动重置事件,这可以使所有线程都被唤醒。
//如果改为自动,那么每次只能唤醒一个线程,程序会出现问题
//这就是成功等待的副作用
g_hWriteEvent = CreateEvent(NULL, TRUE, FALSE, _T("WriteEvent")); for (int i = ; i < THREADCNT;i++){
g_hThreads[i] = CreateThread(NULL, , ThreadProc, NULL, , NULL);
}
} void WriteToBuffer(void)
{
_tprintf(_T("主线程正在写入数据到共享区...\n")); if (!SetEvent(g_hWriteEvent)){
_tprintf(_T("SetEvent失败(%d)\n"),GetLastError());
}
} void CloseEvents()
{
CloseHandle(g_hWriteEvent);
}
第9章 用内核对象进行线程同步(1)_事件对象(Event)的更多相关文章
- Windows核心编程:第9章 用内核对象进行线程同步
Github https://github.com/gongluck/Windows-Core-Program.git //第9章 用内核对象进行线程同步.cpp: 定义应用程序的入口点. // #i ...
- windows核心编程---第八章 使用内核对象进行线程同步
使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...
- C++之内核对象进行线程同步
用户模式下的线程同步机制提供了非常好的性能,但他们也的确存在一些局限性,而且不适用于许多应用程序,例如,对Interlocked系列函数只能对一个值进行操作,它们从来不会把线程切换到等待状态.我们可以 ...
- 《Windows核心编程系列》八谈谈用内核对象进行线程同步
使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...
- 操作系统中的进程同步与Window中利用内核对象进行线程同步的关系
操作系统中为了解决进程间同步问题提出了用信号量机制,信号量可分为四种类型分别是互斥型信号量,记录型信号量,AND型信号量,信号量集. 互斥型信号量 互斥型信号量是资源数量为1的特殊的记录型信号量.表示 ...
- [C++] socket - 6 [API互斥事件对象实现线程同步]
/*API互斥事件对象实现线程同步*/ #include<windows.h> #include<stdio.h> DWORD WINAPI myfun1(LPVOID lpP ...
- [C++] socket - 5 [API事件对象实现线程同步]
/*API事件对象实现线程同步*/ #include<windows.h> #include<stdio.h> DWORD WINAPI myfun1(LPVOID lpPar ...
- Windows核心编程:第8章 用户模式下的线程同步
Github https://github.com/gongluck/Windows-Core-Program.git //第8章 用户模式下的线程同步.cpp: 定义应用程序的入口点. // #in ...
- 第9章 用内核对象进行线程同步(4)_死锁(DeadLock)及其他
9.7 线程同步对象速查表 对象 何时处于未触发状态 何时处于触发状态 成功等待的副作用 进程 进程仍在运行的时候 进程终止的时(ExitProcess.TerminateProcess) 没有 线程 ...
- 第9章 用内核对象进行线程同步(2)_可等待计时器(WaitableTimer)
9.4 可等待的计时器内核对象——某个指定的时间或每隔一段时间触发一次 (1)创建可等待计时器:CreateWaitableTimer(使用时应把常量_WIN32_WINNT定义为0x0400) 参数 ...
随机推荐
- 好文要顶之 --- 简单粗暴地理解 JavaScript 原型链
原型链理解起来有点绕了,网上资料也是很多,每次晚上睡不着的时候总喜欢在网上找点原型链和闭包的文章看,效果极好. 不要纠结于那一堆术语了,那除了让你脑筋拧成麻花,真的不能帮你什么.简单粗暴点看原型链吧, ...
- CSS常用样式(三)
一.2D变换 1.transform 设置或检索对象的转换 取值: none::以一个含六值的(a,b,c,d,e,f)变换矩阵的形式指定一个2D变换,相当于直接应用一个[a,b,c,d,e,f] ...
- SharePoint 2013 为站点配置基于主机标头的双域名
SharePoint的应用中,经常需要配置双域名,为不同的认证方式提供访问入口,下面简单介绍下,如何以主机标头的方式为SharePoint配置双域名: 配置基于主机标头的双域名 1.原本可以访问的测试 ...
- c#程序打包大全
c#程序打包现在分为两种,一种是VS自带的打包方式,还有一种是第三方的打包方式,在VS2013里面是没有自带打包安装部署的,只有第三方的创建. 第三方打包方式很简单,百度Installshield下载 ...
- 之二:CAKeyframeAnimation - 关键帧动画
是CApropertyAnimation的子类,跟CABasicAnimation的区别是:CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CA ...
- App开发流程之使用分类(Category)和忽略编译警告(Warning)
Category使得开发过程中,减少了继承的使用,避免子类层级的膨胀.合理使用,可以在不侵入原类代码的基础上,写出漂亮的扩展内容.我更习惯称之为"分类". Category和Ext ...
- git 错误:
git 错误: $ git commit -afatal: Unable to create 'e:/git/Android/XXXXXX/.git/index.lock': File exists ...
- JavaScript SetInterval与setTimeout使用方法详解
setTimeout和setInterval的语法相同.它们都有两个参数,一个是将要执行的代码字符串,还有一个是以毫秒为单位的时间间隔,当过了那个时间段之后就将执行那段代码.不过这两个函数还是有区别的 ...
- 理解 Statement 和 PreparedStatement
java,servlet中的PreparedStatement 接口继承了Statement,并与之在两方面有所不同:有人主张,在JDBC应用中,如果你已经是稍有水平开发者,你就应该始终以Prepar ...
- jQuery.noConflict() 函数
jQuery.noConflict()函数用于让出jQuery库对变量$(和变量jQuery)的控制权. 一般情况下,在jQuery库中,变量$是变量jQuery的别名,它们之间是等价的,例如jQue ...