您的位置:首页 > 编程语言

《Windows核心编程》读书笔记九 用内核模式进行线程同步

2017-10-11 11:17 387 查看
第九章 用内核对象进行线程同步

本章内容

9.1 等待函数

9.2 等待成功所引起的副作用

9.3 事件内核对象

9.4 可等待的计时器内核对象

9.5 信号量内核对象

9.6 互斥量内核对象

9.7 线程同步对象速查表

9.8 其他的线程同步函数

用户模式下的线程同步高性能,但是存在一些局限。例如无法进行进程间线程的同步,Iterlocked系函数不会把线程切换到等待状态,

进入临界区无法设置最长等待时间等。

内核对象来进行线程同步,功能强大许多。但是唯一的缺点就是性能。

对于线程内核对象可能处于触发(signaled)和未触发(nosignaled)

进程内核对象,在创建时其内部有一个BOOL变量是FALSE, 单进程终止时该内核对象会变成TRUE 表示已经触发

但是这个过程是不可逆的。

以下列出可能处于未触发也可以处于触发状态的内核对象:

进程,线程,作业,文件以及控制台的标准输入流/输出流/错误流

事件,可等待的计时器, 信号量,互斥量

9.1 等待函数

等待函数使一个线程资源进入等待状态,直到指定的内核对象被触发为止。如果在调用一个等待函数时,响应的内核对象已经处于触发状态,那么线程是不会进入等待状态的。

WINBASEAPI
DWORD
WINAPI
WaitForSingleObject(
_In_ HANDLE hHandle,
_In_ DWORD dwMilliseconds
);


hObject用来标识等待的内核对象,可以处于触发或未触发状态。

dwMilliseconds 指定线程最多愿意花多长时间来等待对象被触发

一直等待直到目标进程终止

WaitForSingleObject(hProcess, INFINITE);

DWORD dw = WaitForSingleObject(hProcess, 5000);
switch (dw) {
case WAIT_OBJECT_0:
// The process terminated.
break;
case WAIT_TIMEOUT:
// The process did not terminate within 5000 milliseconds.
break;
case WAIT_FAILED:
// Bad call to function (invalid handle?)
break;
}


WaitForSingleObject的返回值表示为什么调用线程又能继续执行了。

以下函数可以等待多个内核对象的触发状态

WINBASEAPI
DWORD
WINAPI
WaitForMultipleObjects(
_In_ DWORD nCount,
_In_reads_(nCount) CONST HANDLE *lpHandles,
_In_ BOOL bWaitAll,
_In_ DWORD dwMilliseconds
);


nCount表示希望函数检查内核对象的数量。 必须在(1~MAXIMUM_WAIT_OBJECTS之间)

lpHandles 指向内核对象句柄的数组

bWaitAll 是否等待全部触发,FALSE 表示只要一个触发即可

dwMilliseconds 等待时间

一个例子说明WaitForMultipleObjects的返回值

HANDLE h[3];
h[0] = hProcess1;
h[1] = hProcess2;
h[2] = hProcess3;
DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);
switch (dw) {
case WAIT_FAILED:
// Bad call to function (invalid handle?)
break;

case WAIT_TIMEOUT:
// None of the objects became signaled within 5000 milliseconds.
break;

case WAIT_OBJECT_0 + 0:
// The process identified by h[0] (hProcess1) terminated.
break;

case WAIT_OBJECT_0 + 1:
// The process identified by h[1] (hProcess1) terminated.
break;

case WAIT_OBJECT_0 + 2:
// The process identified by h[2] (hProcess1) terminated.
break;
}


如果给bWaitAll传递TRUE那么所有内核对象都触发了以后返回值是 WAIT_OBJECT_0

9.2 等待成功锁引起的副作用

如果WaitForXXXObject成功返回,那么传入的句柄对象发生了变化,成为“等待成功锁引起的副作用”。

有些内核对象在WaitForXXX返回以后会被自动设置为非触发状态(例如自动重置事件对象)

例如一个例子

HANDLE h[2];

WaitForMultipleObjects(2, h, TRUE, INFINITE);

1)两个线程执行同样的代码,

2)其中一个事件触发了,两个线程都能检测到,但是由于另外一个事件未触发,所以线程会继续等待

3)另一个事件也触发了,其中一个线程检测到两个事件都触发以后,将两个事件又设置为非触发状态并返回

4)另一个线程会继续等待直到两个事件同时触发为止。

WaitForMultipleObjects是以原子操做的方式工作的,当他检查内核对象的状态时,任何其他线程都不能在背后修改对象的状态。这就防止了死锁的发生。

9.3 事件内核对象

事件包含一个使用计数器,一个用来标识事件是自动重置还是手动重置的布尔值,以及另一个布尔值用来表示事件有没有被触发。

当一个手动重置事件被触发的时候,正在等待该事件的所有线程都变成可调度状态。

当一个自动重置事件被触发时,只有一个正在等待该事件的线程会变成可调度状态。

事件常用于让一个线程执行初始化工作,然后再触发另一个线程,让它执行剩余的工作。

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateEventW(
_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
_In_ BOOL bManualReset,
_In_ BOOL bInitialState,
_In_opt_ LPCWSTR lpName
);


bManualRest TRUE(手动触发), FALSE(自动触发)

bInitialState 初始状态 TRUE(已触发), FALSE (未触发)

创建以后会返回一个事件内核对象句柄,该事件和当前进程相关联。

还有一个CreateEventEx函数用于创建事件。

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateEventExW(
_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
_In_opt_ LPCWSTR lpName,
_In_ DWORD dwFlags,
_In_ DWORD dwDesiredAccess
);


dwFlags参数可以接受两个位的掩码



dwDesiredAccess 允许指定在创建事件时返回的句柄对事件有何种访问权限。(ex可以限制权限)

如果要调用SetEvent, ResetEvent, PulseEvent。必须使用

EVENT_MODIFY_STATE

其他进程中的线程可以通过多种方式来访问该事件对象,

CreateEvent并在pszName参数中传入相同的值,

使用继承(子进程继承父进程,两者的句柄表中的位置完全一样)

使用DuplicateHandle

或者调用OpenEvent

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
OpenEventW(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ LPCWSTR lpName
);


设置事件为触发状态

BOOL SetEvent(HANDLE hEvent);

设置事件为非触发状态

BOOL ResetEvent(HANDLE hEvent);

自动事件由于等待成功所引起的副作用的影响,当事件被线程等待以后会自动设置为非触发,因此不需要ResetEvent。

HANDLE g_hEvent;

DWORD WINAPI WordCount(PVOID pvParam) {

// Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

// Access the memory block.
// ...

return 0;
}

DWORD WINAPI SpellCheck(PVOID pvParam) {

// Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

// Access the memory block.
// ...

return 0;
}

DWORD WINAPI GrammarCheck(PVOID pvParam) {

// Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

// Access the memory block.
// ...
return 0;
}

int _tmain(int argc, TCHAR* argv[], TCHAR * env[])
{
// Crate the manual-reset, nosignaled event.
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

// Spawn 3 new threads.
HANDLE hThread[3];
DWORD dwThreadID;
hThread[0] = CreateThread(NULL, 0, WordCount, NULL, 0, &dwThreadID);
hThread[1] = CreateThread(NULL, 0, SpellCheck, NULL, 0, &dwThreadID);
hThread[2] = CreateThread(NULL, 0, GrammarCheck, NULL, 0, &dwThreadID);

OpenFileAndReadContentsIntoMemory(...);

// Allow all 3 threads to access the memory.
SetEvent(g_hEvent);
return 0;
}


因为这里使用了手动重置事件,所以当主线程准备好数据以后3个子线程都能同时运行。

如果使用自动重置事件,那么3个子线程只会有一个能继续运行。为了让3个子线程都能执行代码,修改了一下3个子线程的代码

DWORD WINAPI WordCount(PVOID pvParam) {

// Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

// Access the memory block.
// ...
SetEvent(g_hEvent);
return 0;
}

DWORD WINAPI SpellCheck(PVOID pvParam) {

// Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

// Access the memory block.
// ...
SetEvent(g_hEvent);
return 0;
}

DWORD WINAPI GrammarCheck(PVOID pvParam) {

// Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

// Access the memory block.
// ...
SetEvent(g_hEvent);
return 0;
}


这个3个线程都会被系统调用,而且每个线程都能独占的读写资源。

BOOL PulseEvent(HANDLE hEvent);

将一个事件变成触发状态以后立即恢复到未触发状态。

Handshake 示例程序

/******************************************************************************
Module:  Handshake.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/

#include "..\CommonFiles\CmnHdr.h"
#include <windowsx.h>
#include <tchar.h>
#include "Resource.h"

//////////////////////////////////////////////////////////////////////////
#define BUFFERSIZ	1024

// This event is signaled when the client has a request for the server
HANDLE	g_hevtRequestSubmitted;

// This event is signaled when the server has a result for the client
HANDLE	g_hevtResultReturned;

// The buffer shared between the client and server threads
TCHAR	g_szSharedRequestAndResultBuffer[BUFFERSIZ];

// The special value sent from the client that causes the
// server thread to terminate cleanly.
TCHAR	g_szServerShutdown[] = TEXT("Server Shutdown");

// The server thread will check that the main dialog is no longer alive
// When the shutdown message is received.
HWND	g_hMainDlg;

//////////////////////////////////////////////////////////////////////////

// This is the code executed by the server thread
DWORD WINAPI ServerThread(PVOID pvParam) {

// Assume that the server thread is to run forever
BOOL fShutdown = FALSE;

while (!fShutdown) {

// Wait for the client to submit a request
WaitForSingleObject(g_hevtRequestSubmitted, INFINITE);

// Check to see if the client wants the server to terminate
fShutdown =
(g_hMainDlg == NULL) &&
(_tcscmp(g_szSharedRequestAndResultBuffer, g_szServerShutdown) == 0);

if (!fShutdown) {
// Process the client's request (reverse the string)
_tcsrev(g_szSharedRequestAndResultBuffer);
}

// Let the client process the request's result
SetEvent(g_hevtResultReturned);
}

// The client wants us to shut down, exit
return 0;
}

//////////////////////////////////////////////////////////////////////////

BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {

chSETDLGICONS(hwnd, IDI_HANDSHAKE);

// Initialize the edit control with some test data request
Edit_SetText(GetDlgItem(hwnd, IDC_REQUEST), TEXT("Some test data"));

// Store the main dialog window handle
g_hMainDlg = hwnd;

return TRUE;
}

void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {

switch (id) {

case IDCANCEL:
EndDialog(hwnd, id);
break;

case IDC_SUBMIT:		// Submit a request to the server thread

// Copy the request string into the shared data buffer
Edit_GetText(GetDlgItem(hwnd, IDC_REQUEST),
g_szSharedRequestAndResultBuffer,
_countof(g_szSharedRequestAndResultBuffer));

// Let the server thread know that a request is ready in the buffer
// Wait for the server to process the request and give us the result
SignalObjectAndWait(g_hevtRequestSubmitted, g_hevtResultReturned, INFINITE, false);

// Let the user know the result
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 hInstanceExe, HINSTANCE, PTSTR, int) {

// Create & initialize the 2 nonsignaled, auto-reset events
g_hevtRequestSubmitted = CreateEvent(NULL, FALSE, FALSE, NULL);
g_hevtResultReturned = CreateEvent(NULL, FALSE, FALSE, NULL);

// Spawn the server thread
DWORD dwThreadID;
HANDLE hThreadServer = chBEGINTHREADEX(NULL, 0, ServerThread, NULL,
0, &dwThreadID);

// Execute the client thread's user interface
DialogBox(hInstanceExe, MAKEINTRESOURCE(IDD_HANDSHAKE), NULL, Dlg_Proc);
g_hMainDlg = NULL;

// The client's UI is closing, have the server thread shutdown
_tcscpy_s(g_szSharedRequestAndResultBuffer,
_countof(g_szSharedRequestAndResultBuffer), g_szServerShutdown);
SetEvent(g_hevtRequestSubmitted);

HANDLE h[2];
h[0] = g_hevtResultReturned;
h[1] = hThreadServer;
WaitForMultipleObjects(2, h, TRUE, INFINITE);

// Properly clean up everything
CloseHandle(hThreadServer);
CloseHandle(g_hevtRequestSubmitted);
CloseHandle(g_hevtResultReturned);

// The client thread terminates with the whole process
return 0;
}


运行结果



使用CONDITION_VARIABLE实现的handshake例子。虽然用CONDITION_VARIABLE和SRWLOCK效率高,但是使用起来代码的复杂度更高。

Console版本的HandShake

#define  _CRT_SECURE_NO_WARNINGS
#include <tchar.h>
#include <windows.h>
#include <stdio.h>
#include <Shlobj.h>
#include <strsafe.h>
#include <malloc.h>
#include <process.h>
#include <winnt.h>
#include <ctype.h>

CONDITION_VARIABLE		g_cvReturnResult;
CONDITION_VARIABLE		g_cvSubmitRequest;
SRWLOCK					g_srwLock;

TCHAR					g_szSharedBuffer[BUFSIZ];
TCHAR					g_szShutdown[] = TEXT("Server Shutdown");

unsigned __stdcall serverThread(void * pParam) {

bool  bShutdown = false;
_tprintf(TEXT("Server Started! \n"));
while (!bShutdown) {
AcquireSRWLockExclusive(&g_srwLock);
SleepConditionVariableSRW(&g_cvSubmitRequest,
&g_srwLock, INFINITE, 0); // for Exclusive SRWLock

bShutdown =
(_tcscmp(g_szSharedBuffer, g_szShutdown) == 0);

if (!bShutdown)
_tcsrev(g_szSharedBuffer);

ReleaseSRWLockExclusive(&g_srwLock);
WakeConditionVariable(&g_cvReturnResult);
}
_tprintf(TEXT("Server exit with 0! \n"));
return 0;
}

int _tmain(int argc, TCHAR* argv[], TCHAR * env[])
{
// Init Condition variable and srwlock
InitializeConditionVariable(&g_cvReturnResult);
InitializeConditionVariable(&g_cvSubmitRequest);
InitializeSRWLock(&g_srwLock);

_tprintf(TEXT("Handshake Console Version v 0.1\n"));

unsigned int iThreadID;
HANDLE hThreadServer = (HANDLE)_beginthreadex(NULL, 0,
serverThread, NULL, 0, &iThreadID);

bool bShutdown = false;
while (!bShutdown) {

_tprintf(TEXT("Please input the request:\n"));
#ifdef UNICODE
_getws(g_szSharedBuffer);
#else
gets(g_szSharedBuffer);
#endif

bShutdown = (_tcscmp(g_szSharedBuffer, g_szShutdown) == 0);
if (!bShutdown) {
AcquireSRWLockExclusive(&g_srwLock);
WakeConditionVariable(&g_cvSubmitRequest); // signal the server thread
SleepConditionVariableSRW(&g_cvReturnResult, &g_srwLock, INFINITE, 0);
_tprintf(TEXT("Result:\t%s\n"), g_szSharedBuffer);
ReleaseSRWLockExclusive(&g_srwLock);
}
else {
WakeConditionVariable(&g_cvSubmitRequest); // signal the server thread to shutdown.
}
}

WaitForSingleObject(hThreadServer, INFINITE);

// Clean UP
CloseHandle(hThreadServer);
system("pause");
return 0;
}




9.4 可等待的计时器内核对象

可等待的计时器是这样一种内核对象:他们会在某个指定的时间触发,或每间隔一段时间触发。

创建一个可等待的计时器

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateWaitableTimerW(
_In_opt_ LPSECURITY_ATTRIBUTES lpTimerAttributes,
_In_     BOOL bManualReset,
_In_opt_ LPCWSTR lpTimerName
);


打开一个可等待的计时器

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
OpenWaitableTimerW(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ LPCWSTR lpTimerName
);


bManualReset表示要创建的是手动重置还是自动重置的计时器。
手动重置,等待该计时器的所有线程都变成可调度状态

自动重置,只有一个等待该计时器的线程会变成可调度状态

调用SetWaitableTimer设置计时器,能让其触发

WINBASEAPI
BOOL
WINAPI
SetWaitableTimer(
_In_ HANDLE hTimer,
_In_ const LARGE_INTEGER * lpDueTime,
_In_ LONG lPeriod,
_In_opt_ PTIMERAPCROUTINE pfnCompletionRoutine,
_In_opt_ LPVOID lpArgToCompletionRoutine,
_In_ BOOL fResume
);


hTimer :计时器内核对象句柄

pDueTime : 计时器第一次触发的时间应该在什么时候

lPeriod:计时器在第一次触发以后应该以怎样的频度触发。

例如以下代码把计时器第一次触发时间设为2018年1月1日下午1:00,以后没间隔6小时触发一次:

// Declare our local variables.
HANDLE				hTimer;
SYSTEMTIME			st;
FILETIME			ftLocal, ftUTC;
LARGE_INTEGER		liUTC;

// Create an auto-reset timer.
hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

// First signaling is a January 1, 2018, at 1:00 P.M. (local time).
st.wYear			= 2018;	// Year
st.wMonth			= 1;	// January
st.wDayOfWeek		= 0;	// Ignored
st.wDay				= 1;	// The first of the month
st.wHour			= 13;	// 1PM
st.wMinute			= 0;	// 0 minutes into the hour
st.wSecond			= 0;	// 0 seconds into the minute
st.wMilliseconds	= 0;	// 0 milliseconds into the second

SystemTimeToFileTime(&st, &ftLocal);

// Convet local time to UTC time.
LocalFileTimeToFileTime(&ftLocal, &ftUTC);
// Convert FILETIME to LARGE_INTEGER because of different alignment.
liUTC.LowPart = ftUTC.dwLowDateTime;
liUTC.HighPart = ftUTC.dwHighDateTime;

// Set the timer.
SetWaitableTimer(hTimer, &liUTC, 6 * 60 * 60 * 1000,
NULL, NULL, FALSE);


注意这里有一个FILETIME 和LARGE_INTEGER结构转换的问题
因为前者是32位对齐,后者是64位对齐。如果直接传递可能会导致对齐错误抛出一个(EXCEPTION_DATATYPE_MISALIGNMENT异常)

还可以给pDueTime传入一个相对时间,给其传入负值。(100纳秒的整数倍)

1秒 = 1000 毫秒 = 1000 000 微妙 = 10 000 000 (个 100 纳秒)

以下例子把计时器第一次触发时间设置为SetWaitableTimer调用结束的5秒钟后;

// Declare our local variables.
HANDLE				hTimer;
LARGE_INTEGER		li;

// Create an auto-reset timer.
hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

// Set the timer to go off 5 seconds after calling SetWaitableTimer.
// Timer unit is 100 nanoseconds.
const int nTimerUnitsPerSecond = 10000000;

// Negate the time so that SetWaitableTimer knows we
// want relative time instead of absolute time.
li.QuadPart = -(5 * nTimerUnitsPerSecond);

// Set the timer.
SetWaitableTimer(hTimer, &li, 6 * 60 * 60 * 1000,
NULL, NULL, FALSE);


对于一次性计时器,只要给lPeriod 传递0 。然后调用CloseHandle关闭计时器即可。 或者调用SetWaitableTimer来重置计时器。

bResume(TRUE) 会使的计算机结束挂起模式(如果机器处于挂起模式下)并唤醒等待该计时器的线程。

FALSE 计时器会被触发,但是在机器继续执行前(挂起状态),被唤醒的线程都得不到CPU时间。

CancelWaitableTimer

WINBASEAPI
BOOL
WINAPI
CancelWaitableTimer(
_In_ HANDLE hTimer
);


取消计时器内核对象句柄所对应的计时器。

另外每次调用SetWaitableTimer都会重置计时器。

一个倒计时的例子。从9倒计时到0

#define  _CRT_SECURE_NO_WARNINGS
#include <tchar.h>
#include <windows.h>
#include <stdio.h>
#include <Shlobj.h>
#include <strsafe.h>
#include <malloc.h>
#include <process.h>
#include <winnt.h>
#include <ctype.h>

// Declare our local variables.
HANDLE				hTimer;

unsigned __stdcall OnTimer(void * param) {
int nCount = 10;
while (nCount--) {
WaitForSingleObject(hTimer, INFINITE);
_tprintf(TEXT("count:\t%d\n"), nCount);
}
return 0;
}

int _tmain(int argc, TCHAR* argv[], TCHAR * env[])
{

LARGE_INTEGER		li;

// Create an auto-reset timer.
hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

// Set the timer to go off 5 seconds after calling SetWaitableTimer.
// Timer unit is 100 nanoseconds.
const int nTimerUnitsPerSecond = 10000000;

// Negate the time so that SetWaitableTimer knows we
// want relative time instead of absolute time.
li.QuadPart = -(5 * nTimerUnitsPerSecond);

// Set the timer.
SetWaitableTimer(hTimer, &li, 1000,
NULL, NULL, FALSE);

// Create the OnTimer Thread
unsigned int ThreadID;
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, OnTimer, NULL, NULL, &ThreadID);

WaitForSingleObject(hThread, INFINITE);

system("pause");
return 0;
}




9.4.1 让可等待的计时器添加APC调用

APC异步过程调用(asynchronous procedure call)

SetWaitableTimer允许传入一个APC过程,触发了计时器会调用该过程。

当计时器触发时,当且仅当SetWaitableTimer调用的线程处于可提醒状态(Alertable stabe)(SleepEx, WaitForSingleObjectEx, WaitForMultipleObjectEx, MsgWaitForMultipleObjectEx,SignalObjectAndWait而进入的状态)

如果非处于可提醒状态,系统不会把计时器的APC函数添加到队列中。

#define  _CRT_SECURE_NO_WARNINGS
#include <tchar.h>
#include <windows.h>
#include <stdio.h>
#include <Shlobj.h>
#include <strsafe.h>
#include <malloc.h>
#include <process.h>
#include <winnt.h>
#include <ctype.h>
#include <io.h>
#include <fcntl.h>

VOID APIENTRY TimerAPCRountine(PVOID pvArgToCompletionRountine,
DWORD dwTimerLowValue, DWORD dwTimerHighValue) {

FILETIME	ftUTC, ftLocal;
SYSTEMTIME	st;
TCHAR		szBuf[256];

// Put the time in a FILETIME structure.
ftUTC.dwLowDateTime = dwTimerLowValue;
ftUTC.dwHighDateTime = dwTimerHighValue;

// Convert the UTC time to the user's local time.
FileTimeToLocalFileTime(&ftUTC, &ftLocal);

// Convert the FILETIME to the SYSTEMTIME structure
// required by GetDateFormat and GetTimeFormat.
FileTimeToSystemTime(&ftLocal, &st);

// Construct a string with the
// date/time that the timer went off.
GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE,
&st, NULL, szBuf, _countof(szBuf));
_tcscat_s(szBuf, _countof(szBuf), TEXT(" "));
GetTimeFormat(LOCALE_USER_DEFAULT, 0,
&st, NULL, _tcschr(szBuf, TEXT('\0')),
(int)(_countof(szBuf) - _tcslen(szBuf)));

// Show the time to the user.
//MessageBox(NULL, szBuf, TEXT("Timer went off at..."), MB_OK);
_tprintf(TEXT("%s\n"), szBuf);
}

void SomeFunc() {
// Create a timer. (It doesn't matter  whether it's manual-reset
// or auto-reset.)
HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL);

// Set timer to go off in 5 seconds.
LARGE_INTEGER li = { 0 };
SetWaitableTimer(hTimer, &li, 5000, TimerAPCRountine, NULL, FALSE);

// Wait in an alertable state for the timer to go off.
SleepEx(INFINITE, TRUE);

CloseHandle(hTimer);
}

int _tmain(int argc, TCHAR* argv[], TCHAR * env[])
{
_setmode(_fileno(stdout), _O_WTEXT);
SomeFunc();

system("pause");
return 0;
}




不应该同时使用等待函数又同时以可提醒的方式等待一个计时器。

例如

SetWaitableTimer(hTimer,..., TimerAPCRountine,...);

WaitForSingleObjectEx(hTimer, INFINITE, TRUE);

9.4.2 计时器的剩余问题

在通信协议中会大量用到计时器,但是通常为每个请求创建计时器内核对象,将严重影响系统性能。

有一个CreateThreadpoolTimer可以创建线程池函数对应的计时器。

大多数应用程序不使用APC, 而是使用IO完成端口

用户计时器SetTimer :在应用程序中使用大量的用户界面基础设置,从而消费更多的资源。而且通过消息机制触发,只有一个线程能得到通知

(WM_TIMER不一定准时,因为其具有最低的优先级)

可等待计时器是内核对象,可以在多个线程间共享。多个线程可以得到通知。

9.5 信号量内核对象

信号量内核对象用来对资源进行计数,除了使用计数器。还包含(32bit值)一个最大资源计数和当前资源计数。

如果当前资源计数大于0,信号量处于触发状态。

如果当前资源计数等于0,信号量处于未触发状态。

创建信号量

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateSemaphoreW(
_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
_In_     LONG lInitialCount,
_In_     LONG lMaximumCount,
_In_opt_ LPCWSTR lpName
);


psa pszName 前面讲过了。

dwFlags是系统保留的设为0.

参数lMaximumCount 系统能够处理的资源的最大数量

lInitialCoun 初始化有多少资源可用。

例如给服务器进程初始化,没有客户端请求,因此使用一下代码来调用CreateSemaphore

HANDLE hSemaphore = CreateSemaphore(NULL, 0, 5, NULL);

打开一个信号量

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
OpenSemaphoreW(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ LPCWSTR lpName
);


dwDesiredAccess参数指定访问权限

线程通过调用ReleaseSemahore来递增信号量的当前资源计数:

WINBASEAPI
BOOL
WINAPI
ReleaseSemaphore(
_In_ HANDLE hSemaphore,
_In_ LONG lReleaseCount,
_Out_opt_ LPLONG lpPreviousCount
);


lReleaseCount 的值会加到信号量当前资源计数上。

9.6 互斥量内核对象

互斥量(mutex)内核对象用来确保一个线程独占一个资源的访问。

互斥量与临界区的行为完全相同。(内部保护递归计数)

互斥量的规则:

1)如果线程ID为0,那么互斥量不为任何线程所占用,它处于触发状态

2)如果线程ID为非零值,那么一个线程已经占用了该互斥量,它处与未触发状态。

3)与所有其他内核对象不同,操作系统对互斥量进行了特殊处理,允许它们违反一些常规的规则。(递归计数器的存在,运行同一个线程ID多次进入)

创建互斥量

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateMutexW(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,
_In_ BOOL bInitialOwner,
_In_opt_ LPCWSTR lpName
);


bInitialOwner控制互斥量的初始状态。FALSE, 互斥量的线程ID和递归计数都被设为0.处于触发状态。

给bInitialOwner穿TRUE,那么对象的线程ID被设为调用线程的ID,递归计数器被设为1.(未触发状态)

或者使用CreateMutexEx

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateMutexExW(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,
_In_opt_ LPCWSTR lpName,
_In_ DWORD dwFlags,
_In_ DWORD dwDesiredAccess
);


dwDesiredAccess指定访问权限

dwFlags(代替bInitialOwned) 0表示FALSE, CREATE_MUTEX_INITIAL_OWNER等价于TRUE

另一个进程可以调用OpenMutex来得到一个已经存在的互斥量句柄。

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
OpenMutexW(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ LPCWSTR lpName
);


BOOL ReleaseMutex(HANDLE hMutex);

使互斥量对象的递归计数器减1, 当递归计数器为0时,还会设置线程ID为0,这就触发了对象。

9.6.1 遗弃问题

互斥量具有线程所有权的功能,即使未触发也能多次进入。

如果占用互斥量的线程在释放互斥量之前终止(ExitThread ,TerminateThread ,ExitProcess 或TerminateProcess)

系统认为互斥量被遗弃(abandoned) 此时会自动将互斥量线程ID设为0,递归计数器设为0,并检查有没有正在等待该互斥量的线程。

等待函数返回WAIT_ABANDONED(只适用互斥量)

9.6.2 互斥量与关键段(临界区)的对比



互斥量的任意时间长度等待修正为: WaitForSingleObject(hmtx, dwMilliseconds);

9.6.3 Queue 示例程序

本章的Queue使用了互斥量和信号量来对一个队列的简单数据元素进行控制。

/******************************************************************************
Module:  Queue.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/

#include "..\CommonFiles\CmnHdr.h"
#include <windowsx.h>
#include <tchar.h>
#include <StrSafe.h>
#include "Resource.h"

//////////////////////////////////////////////////////////////////////////

class CQueue {
public:
struct ELEMENT {
int m_nThreadNum, m_nRequestNum;
// Other element data should go there
};
typedef ELEMENT * PELEMENT;

private:
PELEMENT	m_pElements;		// Array of elements to be processed
int			m_nMaxElements;		// Maximum # of elements in the array
HANDLE		m_h[2];				// Mutex & semaphore handles
HANDLE		&m_hmtxQ;			// Reference to m_h[0]
HANDLE		&m_hsemNumElements;	// Reference to m_h[1]

public:
CQueue(int nMaxElements);
~CQueue();

BOOL Append(PELEMENT pElement, DWORD dwMilliseconds);
BOOL Remove(PELEMENT pElement, DWORD dwMilliseconds);
};

//////////////////////////////////////////////////////////////////////////

CQueue::CQueue(int nMaxElements)
: m_hmtxQ(m_h[0]),
m_hsemNumElements(m_h[1]) {

m_pElements = (PELEMENT)
HeapAlloc(GetProcessHeap(), 0, sizeof(ELEMENT) * nMaxElements);
m_nMaxElements = nMaxElements;
m_hmtxQ = CreateMutex(NULL, FALSE, NULL);
m_hsemNumElements = CreateSemaphore(NULL, 0, nMaxElements, NULL);
}

//////////////////////////////////////////////////////////////////////////

CQueue::~CQueue() {

CloseHandle(m_hsemNumElements);
CloseHandle(m_hmtxQ);
HeapFree(GetProcessHeap(), 0, m_pElements);
}

//////////////////////////////////////////////////////////////////////////

BOOL CQueue::Append(PELEMENT pElement, DWORD dwTimeout) {

BOOL fOk = FALSE;
DWORD dw = WaitForSingleObject(m_hmtxQ, dwTimeout);

if (dw == WAIT_OBJECT_0) {
// This thread has exclusive access to the queue

// Increment the number of elements in the queue
LONG lPrevCount;
fOk = ReleaseSemaphore(m_hsemNumElements, 1, &lPrevCount);
if (fOk) {
// The queue is not full, append the new element
m_pElements[lPrevCount] = *pElement;
}
else {

// The queue is full, set the error code and return failure
SetLastError(ERROR_DATABASE_FULL);
}

// Allow other threads to access the queue
ReleaseMutex(m_hmtxQ);
}
else {
// Timeout, set error code and return failure
SetLastError(ERROR_TIMEOUT);
}

return fOk;		// Call GetLastError for more info
}

//////////////////////////////////////////////////////////////////////////

BOOL CQueue::Remove(PELEMENT pElement, DWORD dwTimeout) {

// Wait for exclusive access to queue and for queue to have element.
BOOL fOk = (WaitForMultipleObjects(_countof(m_h), m_h, TRUE, dwTimeout)
== WAIT_OBJECT_0);

if (fOk) {
// The queue has an element, pull it from the queue
*pElement = m_pElements[0];

// Shift the remaining elements down
MoveMemory(&m_pElements[0], &m_pElements[1],
sizeof(ELEMENT) * (m_nMaxElements - 1));

// Allow other threads to access the queue
ReleaseMutex(m_hmtxQ);
}
else {
// Timeout, set error code and return failure
SetLastError(ERROR_TIMEOUT);
}

return fOk;		// Call GetLastError for more info
}

//////////////////////////////////////////////////////////////////////////

CQueue g_q(10);						// The shared queue
volatile LONG g_fShutdown = FALSE;	// Signals client/server threads to die
HWND g_hwnd;						// How client/server threads give status

// Handles to all client/server threads & number of client/server threads
HANDLE	g_hThreads[MAXIMUM_WAIT_OBJECTS];
int		g_nNumThreads = 0;

//////////////////////////////////////////////////////////////////////////

DWORD WINAPI ClientThread(PVOID pvParam) {

int nThreadNum = PtrToUlong(pvParam);
HWND hwndLB = GetDlgItem(g_hwnd, IDC_CLIENTS);

int nRequestNum = 0;
while (1 != InterlockedCompareExchange(&g_fShutdown, 0, 0)) {

// Keep track of the current processed element
nRequestNum++;

TCHAR sz[1024];
CQueue::ELEMENT e = { nThreadNum, nRequestNum };

// Try to put an element on the queue
if (g_q.Append(&e, 200)) {

// Indicate which thread sent it and which request
StringCchPrintf(sz, _countof(sz), TEXT("Sending %d:%d"),
nThreadNum, nRequestNum);
}
else {

// Couldn't put an element on the queue
StringCchPrintf(sz, _countof(sz), TEXT("Sending %d:%d (%s)"),
nThreadNum, nRequestNum, (GetLastError() == ERROR_TIMEOUT)
? TEXT("timeout") : TEXT("full"));
}

// Show result of appending element
ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));
Sleep(2500);	// Wait before appending another element
}

return 0;
}

//////////////////////////////////////////////////////////////////////////

DWORD WINAPI ServerThread(PVOID pvParam) {

int nThreadNum = PtrToUlong(pvParam);
HWND hwndLB = GetDlgItem(g_hwnd, IDC_SERVERS);

while (1 != InterlockedCompareExchange(&g_fShutdown, 0, 0)) {

TCHAR sz[1024];
CQueue::ELEMENT e;

// Try to get an element from the queue
if (g_q.Remove(&e, 5000)) {

// Indicate which thread is processing it, which thread
// sent it and which request we're processing
StringCchPrintf(sz, _countof(sz), TEXT("%d: Processing %d:%d"),
nThreadNum, e.m_nThreadNum, e.m_nRequestNum);

// The server takes some time to process the request
Sleep(2000 * e.m_nThreadNum);
}
else {
// Couldn't get an element from the queue
StringCchPrintf(sz, _countof(sz), TEXT("%d: (timeout)"), nThreadNum);
}

// Show result of processing element
ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));
}

return 0;
}

//////////////////////////////////////////////////////////////////////////

BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {

chSETDLGICONS(hwnd, IDI_QUEUE);

g_hwnd = hwnd;		// Used by client/server threads to show status

DWORD dwThreadID;

// Create the client threads
for (int x = 0; x < 4; x++)
g_hThreads[g_nNumThreads++] =
chBEGINTHREADEX(NULL, 0, ClientThread, (PVOID)(INT_PTR)x,
0, &dwThreadID);

// Create the server threads
for (int x = 0; x < 2; x++)
g_hThreads[g_nNumThreads++] =
chBEGINTHREADEX(NULL, 0, ServerThread, (PVOID)(INT_PTR)x,
0, &dwThreadID);

return TRUE;
}

//////////////////////////////////////////////////////////////////////////

void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {

switch (id) {
case IDCANCEL:
EndDialog(hwnd, id);
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 pszCmdLine, int) {

DialogBox(hInstExe, MAKEINTRESOURCE(IDD_QUEUE), NULL, Dlg_Proc);
InterlockedExchange(&g_fShutdown, TRUE);

// Wait for all the threads to terminate & then cleanup
WaitForMultipleObjects(g_nNumThreads, g_hThreads, TRUE, INFINITE);
while (g_nNumThreads--)
CloseHandle(g_hThreads[g_nNumThreads]);

return 0;
}

//////////////////////////////// End of File //////////////////////////////////




9.7 线程同步速查表



9.8 其他线程同步函数

9.8.1 异步设备I/O

异步设备IO(asynchronous device I/O)允许线程开始读取操作或写入操作,但不必等待读取操作或写入操作完成。

设备对象是是可同步的内核对象,可以调用WaitForSingleObject并传入句柄,套接字,通信端口等。

9.8.2 WaitForInputIdle函数

线程可以调用此函数将自己挂起

WINUSERAPI
DWORD
WINAPI
WaitForInputIdle(
_In_ HANDLE hProcess,
_In_ DWORD dwMilliseconds);


常用于等待子进程,父进程知道子进程已经初始化完毕的唯一方法,就是等待子进程,直到它不再处理任何输入为止。

当我们要强制在应用程序中输入一些按键的时候,也可以使用WaitForInputIdle。

当向目标进程发送一系列按键消息以后,调用WaitForInputIdle等待其处理完按键消息,然后再发送后续的按键消息。

9.8.3 MsgWaitForMultipleObjects(Ex)函数

WINUSERAPI
DWORD
WINAPI
MsgWaitForMultipleObjects(
_In_ DWORD nCount,
_In_reads_opt_(nCount) CONST HANDLE *pHandles,
_In_ BOOL fWaitAll,
_In_ DWORD dwMilliseconds,
_In_ DWORD dwWakeMask);

WINUSERAPI
DWORD
WINAPI
MsgWaitForMultipleObjectsEx(
_In_ DWORD nCount,
_In_reads_opt_(nCount) CONST HANDLE *pHandles,
_In_ DWORD dwMilliseconds,
_In_ DWORD dwWakeMask,
_In_ DWORD dwFlags);


函数功能:阻塞时仍可以响应消息

MsgWaitForMultipleObjects()函数类似WaitForMultipleObjects(),

但它会在“对象被激发”或“消息到达队列”时被唤醒而返回。

MsgWaitForMultipleObjects()多接收一个参数,允许指定哪些消息是观察对象。

一个应用的例子 该函数同时等待对象,若有消息到底也返回。运行主线程处理消息后继续等待。

DWORD dwRet = 0;
MSG msg;
DWORD dwStartTime = GetTickCount();
while (TRUE)
{
//超时判断  5s
dwRet = GetTickCount() - dwStartTime;
if ((GetTickCount() - dwStartTime) > 10000)
{
AfxMessageBox(_T("获取数据超时,请检测设备网络连接!"), MB_OK | MB_ICONERROR);
return NULL;
}

//wait for m_hThread to be over,and wait for
//QS_ALLINPUT(Any message is in the queue)
//dwRet = WaitForSingleObject(g_hRetEvent, INFINITE);
dwRet = MsgWaitForMultipleObjects(1, &g_hRetEvent, FALSE, 100, QS_ALLINPUT);
switch (dwRet)
{
case WAIT_OBJECT_0: //返回数据达到
break; //break the loop
case WAIT_OBJECT_0 + 1: //界面消息
//get the message from Queue
//and dispatch it to specific window
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
continue;
case WAIT_TIMEOUT: //超时
continue;
default:
AfxMessageBox(_T("数据获取失败,未知错误!"), MB_OK | MB_ICONERROR);
return NULL;
break; // unexpected failure
}
break;
}


9.8.4 WaitForDebugEvent 函数

调试器Attach到被调试程序以后,调用WaitForDebugEvent来等待调试事件。

WINBASEAPI
BOOL
APIENTRY
WaitForDebugEvent(
_Out_ LPDEBUG_EVENT lpDebugEvent,
_In_ DWORD dwMilliseconds
);


9.8.5 SignalObjectAndWait函数

WINBASEAPI
DWORD
WINAPI
SignalObjectAndWait(
_In_ HANDLE hObjectToSignal,
_In_ HANDLE hObjectToWaitOn,
_In_ DWORD dwMilliseconds,
_In_ BOOL bAlertable
);
使用一个原子操作来触发一个内核对象,并等待另一个内核对象。

hObjectToSignal必须是一个互斥量,信号量或事件。(其他任何对象将导致函数返回WAIT_FAILD)调用GetLastError返回ERROR_INVALID_HANDLE.

hObjectToWaitOn可以是互斥量,信号量,事件,进程,线程,作业,控制台输入变更通知,作业。等等

dwMilliseconds 函数最多花多长时间来等待。

bAlertable表示当线程处于等待状态的时候,是否能够堆添加到队列中的异步过程调用进行处理。

返回值:WAIT_OBJECT_0, WAIT_TIMEOUT, WAIT_FAILED, WAIT)ABANDONED, WAIT_IO_COMPLETION

配合PulseEvent使用。

SignalObjectAndWait释放一个对象,同时立即等待(原子方式)

能确保其100%能看见别的线程调用的PulseEvent。

9.8.6 使用等待链遍历API来检测死锁

Vista系统以上提供了等待链遍历(Wait Chain Traversal, WCT)API,这些函数可以让我们列出所有的锁,并检测进程内部,甚至是进程之间的死锁。





LockCop示例程序

LockCop展示如何使用WCT函数来创建一个非常有用的工具。

等待链

一条等待链是一个序列,在这个序列中线程和同步对象交替出现,每个线程等待它后面的对象,而该对象却为等待链中更后面的线程所占用。

例如3212正在等待线程2260释放一个关键段,而线程2260正在等待线程3212释放另外一个关键段。这就是典型的死锁。

代码LockCop.cpp

/******************************************************************************
Module:  LockCop.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/

#include "..\CommonFiles\CmnHdr.h"     /* See Appendix A. */
#include "..\CommonFiles\ToolHelp.h"
#include "ChainParser.h"
#include "resource.h"

#include <windowsx.h>
#include <tchar.h>
#include <StrSafe.h>

///////////////////////////////////////////////////////////////////////////////

// Global Variables
HINSTANCE		g_hInstance;
HWND			g_hDlg;

#define DETAILS_CTRL	GetDlgItem(g_hDlg, IDC_EDIT_DETAILS)
//////////////////////////////////////////////////////////////////////////

// Adds a String to the "Details" edit control
void AddText(PCTSTR pszFormat, ...) {

va_list argList;
va_start(argList, pszFormat);

TCHAR sz[20 * 1024];

Edit_GetText(DETAILS_CTRL, sz, _countof(sz));
_vstprintf_s(
_tcschr(sz, TEXT('\0')), _countof(sz) - _tcslen(sz),
pszFormat, argList);
Edit_SetText(DETAILS_CTRL, sz);
va_end(argList);
}

//////////////////////////////////////////////////////////////////////////

void OnRefreshProcesses()
{
HWND hwndList = GetDlgItem(g_hDlg, IDC_COMBO_PROCESS);
SetWindowRedraw(hwndList, FALSE);
ComboBox_ResetContent(hwndList);

CToolhelp thProcesses(TH32CS_SNAPPROCESS);
PROCESSENTRY32 pe = { sizeof(pe) };
BOOL fOk = thProcesses.ProcessFirst(&pe);
for (; fOk; fOk = thProcesses.ProcessNext(&pe)) {
TCHAR sz[1024];

// Place the process name(without its path) & ID in the list
PCTSTR pszExeFile = _tcschr(pe.szExeFile, TEXT('\\'));
if (pszExeFile == NULL) {
pszExeFile = pe.szExeFile;
}
else {
pszExeFile++;		// skip over the slash
}

StringCchPrintf(sz, _countof(sz), TEXT("%04u - %s"), pe.th32ProcessID, pszExeFile);
int n = ComboBox_AddString(hwndList, sz);

// Associate the process ID with the added item
ComboBox_SetItemData(hwndList, n, pe.th32ProcessID);
}
ComboBox_SetCurSel(hwndList, 0);	// Select the first entry

// Simulate the user selecting this first item so that the
// results pane shows something interesting
FORWARD_WM_COMMAND(g_hDlg, IDC_COMBO_PROCESS,
hwndList, CBN_SELCHANGE, SendMessage);

SetWindowRedraw(hwndList, TRUE);
InvalidateRect(hwndList, NULL, FALSE);
}

//////////////////////////////////////////////////////////////////////////

void OnUpdateLocks()
{
SetWindowText(DETAILS_CTRL, TEXT(""));	// Clear the output box

// GetCurrent process from the combo box
HWND hwndCtl = GetDlgItem(g_hDlg, IDC_COMBO_PROCESS);
DWORD dwSelection = ComboBox_GetCurSel(hwndCtl);
DWORD PID = (DWORD)ComboBox_GetItemData(hwndCtl, dwSelection);

AddText(TEXT("Thread in process %u\r\n"), PID);

CChainParser parser(DETAILS_CTRL);
parser.ParseThreads(PID);
}

//////////////////////////////////////////////////////////////////////////

void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {

switch (id) {
case IDOK:
case IDCANCEL:
// User has clicked on the OK button
// or dismissed the dialog with ESCAPE
EndDialog(hwnd, id);
break;

case IDC_COMBO_PROCESS:
if (codeNotify == CBN_SELCHANGE) {
OnUpdateLocks();
}
break;

case IDC_BTN_REFRESH:
OnRefreshProcesses();
break;

case IDC_BTN_UPDATE:
OnUpdateLocks();
break;
}
}

//////////////////////////////////////////////////////////////////////////

BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {

chSETDLGICONS(hwnd, IDI_LOCKCOP);

// Keep track of the main dialog window handle
g_hDlg = hwnd;

// Have the results window use a fixed-pitch font
SetWindowFont(GetDlgItem(hwnd, IDC_EDIT_DETAILS),
GetStockFont(ANSI_FIXED_FONT), FALSE);

// Fill up the process combo-box
OnRefreshProcesses();

return TRUE;
}

//////////////////////////////////////////////////////////////////////////

INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

switch (uMsg) {
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
}

return FALSE;
}

//////////////////////////////////////////////////////////////////////////

int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow) {

UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);

// Keep track of the module handle
g_hInstance = hInstance;

// Enabling the debug privilege allows the application to see
// Information about service applications
CToolhelp::EnablePrivilege(SE_DEBUG_NAME, TRUE);

// Show main window
DialogBox(hInstance, MAKEINTRESOURCE(IDD_LOCKCOP), NULL, Dlg_Proc);

// Restore privileges
// Even though it is not really important since the process is existing
CToolhelp::EnablePrivilege(SE_DEBUG_NAME, FALSE);

return 0;
}


运行结果(检测例子BadLock)



LockCop不支持WaitForMultileObjects
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  windows 线程同步