WindowsAPI查缺补漏-多线程

线程创建与销毁

CreateThread

在当前进程创建新线程。

1
2
3
4
5
6
7
8
HANDLE WINAPI CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程安全属性结构
_In_ SIZE_T dwStackSize, //线程栈空间大小
_In_ LPTHREAD_START_ROUTINE lpStartAddress, //线程函数指针
_In_opt_ LPVOID lpParameter, //传递给线程函数的参数
_In_ DWORD dwCreationFlags, //线程创建标志
_Out_opt_ LPDWORD lpThreadId //返回线程ID
)

例子:

1
2
3
4
5
6
HANDLE hThread;
hThread = ::CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
if (hThread != NULL) {
::CloseHandle(hThread);
hThread = NULL;
};

SECURITY_ATTRIBUTES

1
2
3
4
5
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength; //该结构大小
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle; //创建新进程时 是否继承返回的句柄
}SECURITY_ATTRIBUTES, * PSECURITY_ATTRIBUTES;

例子:

1
2
3
4
5
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
hThread = ::CreateThread(&sa, 0, ThreadProc, NULL, 0, NULL);

其中线程函数定义格式为:

1
DWORD WINAPI ThreadProc(LPVOID lpParameter);

ExitThread

一般就用return结束线程,这个函数不推荐,是强制线程终止。C++资源如类对象等不会被释放。只能用于终结当前线程。

1
2
3
VOID ExitThread(
_In_ DWORD dwExitCode //退出码
)

TerminateThread

终结线程,可终止任何线程。这个是异步的,函数返回时不保证已终结完毕,需要WaitForSingleObjectGetExitCodeThread检测。被终止的线程是被强制终结的,自己收不到被终止的通知,无法正确清理并结束,且线程自己不能阻止被终结。当不是拥有被终结线程的进程终结时,该线程栈不会被立即销毁,怕其他线程引用就会发生访问违例。

1
2
3
4
BOOL WINAPI TerminateThread(
_Inout_ HANDLE hThread, //要终止的线程句柄
_In_ DWORD dwExitCode //线程退出码
)

GetExitCodeThread

任何其他线程可通过这个函数检查指定线程是否已终止运行。已终止时返回退出码,尚未终止返回STILL_ACTIVE。

1
2
3
4
BOOL WINAPI GetExitCodeThread(
_In_ HANDLE hThread, //线程句柄
_Out_ LPDWORD lpExitCode //线程退出码
)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <windows.h>
#include "resource.h"
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// 常量定义
#define F_START 1 // 开始计数
#define F_STOP 2 // 停止计数
// 全局变量
HWND g_hwndDlg;
INT g_nOption; // 标志
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
// 创建模态对话框
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HWND hwndBtnStart, hwndBtnStop, hwndBtnPause, hwndBtnContinue;
HANDLE hThread = NULL;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
hwndBtnStart = GetDlgItem(hwndDlg, IDC_BTN_START);
hwndBtnStop = GetDlgItem(hwndDlg, IDC_BTN_STOP);
hwndBtnPause = GetDlgItem(hwndDlg, IDC_BTN_PAUSE);
hwndBtnContinue = GetDlgItem(hwndDlg, IDC_BTN_CONTINUE);
// 禁用停止、暂停、继续按钮
EnableWindow(hwndBtnStop, FALSE);
EnableWindow(hwndBtnPause, FALSE);
EnableWindow(hwndBtnContinue, FALSE);
return TRUE;
};
case WM_COMMAND:{
switch (LOWORD(wParam)) {
case IDC_BTN_START: {
g_nOption = 0;
g_nOption |= F_START;
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); // 创建一个子线程
if (hThread != NULL) {
CloseHandle(hThread);
hThread = NULL;
};
EnableWindow(hwndBtnStart, FALSE);
EnableWindow(hwndBtnStop, TRUE);
EnableWindow(hwndBtnPause, TRUE);
break;
};
case IDC_BTN_STOP: {
g_nOption |= F_STOP;
EnableWindow(hwndBtnStart, TRUE);
EnableWindow(hwndBtnStop, FALSE);
EnableWindow(hwndBtnPause, FALSE);
EnableWindow(hwndBtnContinue, FALSE);
break;
};
case IDC_BTN_PAUSE: {
g_nOption &= ~F_START;
EnableWindow(hwndBtnStart, FALSE);
EnableWindow(hwndBtnStop, TRUE);
EnableWindow(hwndBtnPause, FALSE);
EnableWindow(hwndBtnContinue, TRUE);
break;
};
case IDC_BTN_CONTINUE: {
g_nOption |= F_START;
EnableWindow(hwndBtnStart, FALSE);
EnableWindow(hwndBtnStop, TRUE);
EnableWindow(hwndBtnPause, TRUE);
EnableWindow(hwndBtnContinue, FALSE);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
};
return TRUE;
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
INT n = 0;
while (!(g_nOption & F_STOP))
if (g_nOption & F_START)
SetDlgItemInt(g_hwndDlg, IDC_EDIT_COUNT, n++, FALSE);
return 0;
};

其他线程操作

ResumeThread

创建线程时记录被挂起次数,为0时才会继续运行,被挂起一次加一,被恢复一次减一。当CreateThread时dwCreationFlags设置CREATE_SUSPENDED时挂起次数为1。

1
2
3
DWORD WINAPI ResumeThread(
_In_ HANDLE hThread
)//成功返回先前挂起次数 失败-1

SuspendThread

挂起运行中的函数,挂起计数加一。任何线程可用这个挂起任何线程,也可以挂起自己但显然没法恢复自己了,一个线程可最多被挂起MAXIMUM_SUSPEND_COUNT次,即127次。

1
2
3
DWORD WINAPI SuspendThread(
_In_ HANDLE hThread
)

Sleep

暂停当前线程指定毫秒数,0表示放弃本线程在当前CPU时间片剩余时间,系统可以转去调度其他线程,待该线程轮流到下一个时间片再继续执行。

1
2
3
VOID WINAPI Sleep(
_In_ DWORD dwMilliseconds
)

线程间通信

简单的有全局变量法,但有同步问题。

自定义消息

用户可以在WM_USER~0x7FFF和WM_APP~0xBFFF之间自定义消息ID。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <windows.h>
#include "resource.h"
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// 自定义消息,用于计数线程向显示线程发送消息报告工作进度(这两个都是工作线程)
#define WM_WORKPROGRESS (WM_APP + 1)
// 自定义消息,计数线程发送消息给主线程告知工作已完成
#define WM_CALCOVER (WM_APP + 2)
// 全局变量
HWND g_hwndDlg;
BOOL g_bRuning; // 计数线程没有消息循环,主线程通过把这个标志设为FALSE通知其终止线程
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 线程函数声明
DWORD WINAPI ThreadProcShow(LPVOID lpParameter); // 把数值显示到编辑控件中
DWORD WINAPI ThreadProcCalc(LPVOID lpParameter); // 模拟执行一项任务,定时把一个数加1
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HANDLE hThreadShow, hThreadCalc;
static DWORD dwThreadIdShow;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
// 禁用停止按钮
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_STOP), FALSE);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_START: {
g_bRuning = TRUE;
// 创建显示线程和计数线程
hThreadShow = CreateThread(NULL, 0, ThreadProcShow, NULL, 0, &dwThreadIdShow);
hThreadCalc = CreateThread(NULL, 0, ThreadProcCalc, (LPVOID)dwThreadIdShow, 0, NULL);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), FALSE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_STOP), TRUE);
break;
};
case IDC_BTN_STOP: {
// 通知计数线程退出
g_bRuning = FALSE;
// 通知显示线程退出
PostThreadMessage(dwThreadIdShow, WM_QUIT, 0, 0);
if (hThreadShow != NULL) {
CloseHandle(hThreadShow);
hThreadShow = NULL;
};
if (hThreadCalc != NULL) {
CloseHandle(hThreadCalc);
hThreadCalc = NULL;
};
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), TRUE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_STOP), FALSE);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_CALCOVER: {
if (hThreadShow != NULL) {
CloseHandle(hThreadShow);
hThreadShow = NULL;
};
if (hThreadCalc != NULL) {
CloseHandle(hThreadCalc);
hThreadCalc = NULL;
};
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), TRUE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_STOP), FALSE);
MessageBox(hwndDlg, TEXT("计数线程工作已完成"), TEXT("提示"), MB_OK);
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProcShow(LPVOID lpParameter) {
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) != 0)
switch (msg.message) {
case WM_WORKPROGRESS: {
SetDlgItemInt(g_hwndDlg, IDC_EDIT_COUNT, (UINT)msg.wParam, FALSE);
break;
};
};
return msg.wParam;
};
DWORD WINAPI ThreadProcCalc(LPVOID lpParameter) {
// lpParameter参数是传递过来的显示线程ID
DWORD dwThreadIdShow = (DWORD)lpParameter;
INT nCount = 0;
while (g_bRuning) {
PostThreadMessage(dwThreadIdShow, WM_WORKPROGRESS, nCount++, NULL);
Sleep(50);
// nCount到达100,说明工作完成
if (nCount > 100) {
// 通知显示线程退出
PostThreadMessage(dwThreadIdShow, WM_QUIT, 0, 0);
// 发送消息给主线程告知工作已完成
PostMessage(g_hwndDlg, WM_CALCOVER, 0, 0);
// 本计数线程也退出
g_bRuning = FALSE;
break;
};
};
return 0;
};

这个方法不好,不停检查时会导致CPU占用过高。而且如果被挂起的进程在分配堆,则该堆将被锁定,其他访问的进程需要等待,形成死锁。

事件对象

事件对象是内核对象的一种,内核对象还包括互斥量对象、信号量对象、可等待计时器对象、进程对象、文件对象、文件映射对象、I/O完成端口对象、邮件槽对象、管道对象等。

CreateEvent

创建事件对象。

1
2
3
4
5
6
HANDLE WINAPI CreateEvent(
_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes, //NULL默认不能被子进程继承
_In_ BOOL bManualReset, //TRUE手动重置 FALSE自动重置
_In_ BOOL bInitialState, //初始状态 TRUE有信号 FALSE无信号
_In_opt_ LPCTSTR lpName //名称
)

不存在则创建。已存在则获取句柄,且GetLastError为ERROR_ALREAD_EXISTS。存在其他同名内核对象则返回NULL,GetLastError为ERROR_INVALID_HANDLE。无论打开还是创建后都要CloseHandle。打开或创建后事件对象引用都加一。

SetEvent

设置为有信号。

1
2
3
BOOL WINAPI SetEvent(
_In_ HANDLE hEvent
)

ResetEvent

设置为无信号。自动重置事件在被等待到后自动重置为无信号,手动重置事件需要用这个函数重置。

1
2
3
BOOL WINAPI ResetEvent(
_In_ HANDLE hEvent
)

OpenEvent

打开事件对象。

1
2
3
4
5
HANDLE WINAPI OpenEvent(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle, //创建新进程时是否继承返回的句柄
_In_ LPCTSTR lpName //名称
)

没找到返回NULL,GetLastError为ERROR_FILE_NOT_FOUND;找到同名的其他类型内核对象返回NULL,GetLastError为ERROR_INVALID_HANDLE;名称相同类型相同的返回事件对象句柄。

WaitForSingleObject

等待指定事件对象变为有信号状态。

1
2
3
4
DWORD WINAPI WaitForSingleObject(
_In_ HANDLE hHandle,
_In_ DWORD dwMilliseconds //超时事件 单位毫秒 无限长INFINITE
)

返回值:等待的对象变为有信号状态WAIT_OBJECT_0,超时WAIT_TIMEOUT,失败WAIT_FAILED。

WaitForSingleObject

测试多个事件对象的状态。

1
2
3
4
5
6
DWORD WINAPI WaitForMultipleObjects(
_In_ DWORD nCount, //要等待的对象句柄数 最大MAXIMUM_WAIT_OBJECTS
_In_ CONST PHANDLE lpHandle, //要等待的对象句柄数组
_In_ BOOL bWaitAll, //是否等待数组中所有对象状态都变为有信号
_In_ DWORD dwMilliseconds //超时时间
)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <windows.h>
#include "resource.h"
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// 全局变量
HWND g_hwndDlg;
HANDLE g_hEventStart; // 事件对象句柄,作为开始标志
HANDLE g_hEventStop; // 事件对象句柄,作为停止标志
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HWND hwndBtnStart, hwndBtnStop, hwndBtnPause, hwndBtnContinue;
HANDLE hThread = NULL;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
hwndBtnStart = GetDlgItem(hwndDlg, IDC_BTN_START);
hwndBtnStop = GetDlgItem(hwndDlg, IDC_BTN_STOP);
hwndBtnPause = GetDlgItem(hwndDlg, IDC_BTN_PAUSE);
hwndBtnContinue = GetDlgItem(hwndDlg, IDC_BTN_CONTINUE);
// 禁用停止、暂停、继续按钮
EnableWindow(hwndBtnStop, FALSE);
EnableWindow(hwndBtnPause, FALSE);
EnableWindow(hwndBtnContinue, FALSE);
// 创建事件对象
g_hEventStart = CreateEvent(NULL, TRUE, FALSE, NULL);
g_hEventStop = CreateEvent(NULL, TRUE, FALSE, NULL);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_START: {
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
if (hThread != NULL) {
CloseHandle(hThread);
hThread = NULL;
};
SetEvent(g_hEventStart); // 设置开始标志
ResetEvent(g_hEventStop); // 清除停止标志
EnableWindow(hwndBtnStart, FALSE);
EnableWindow(hwndBtnStop, TRUE);
EnableWindow(hwndBtnPause, TRUE);
break;
};
case IDC_BTN_STOP: {
SetEvent(g_hEventStop); // 设置停止标志
EnableWindow(hwndBtnStart, TRUE);
EnableWindow(hwndBtnStop, FALSE);
EnableWindow(hwndBtnPause, FALSE);
EnableWindow(hwndBtnContinue, FALSE);
break;
};
case IDC_BTN_PAUSE: {
ResetEvent(g_hEventStart); // 清除开始标志
EnableWindow(hwndBtnStart, FALSE);
EnableWindow(hwndBtnStop, TRUE);
EnableWindow(hwndBtnPause, FALSE);
EnableWindow(hwndBtnContinue, TRUE);
break;
};
case IDC_BTN_CONTINUE: {
SetEvent(g_hEventStart); // 设置开始标志
EnableWindow(hwndBtnStart, FALSE);
EnableWindow(hwndBtnStop, TRUE);
EnableWindow(hwndBtnPause, TRUE);
EnableWindow(hwndBtnContinue, FALSE);
break;
};
case IDCANCEL: {
// 关闭事件对象句柄
CloseHandle(g_hEventStart);
CloseHandle(g_hEventStop);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
INT n = 0;
while (WaitForSingleObject(g_hEventStop, 0) != WAIT_OBJECT_0) // 是否设置了停止标志
if (WaitForSingleObject(g_hEventStart, 100) == WAIT_OBJECT_0) // 是否设置了开始标志
SetDlgItemInt(g_hwndDlg, IDC_EDIT_COUNT, n++, FALSE);
return 0;
};

手动重置事件

对于手动重置事件,需要被等待后ResetEvent重置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <windows.h>
#include "resource.h"
// 全局变量
HWND g_hwndDlg;
HANDLE g_hEvent;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD WINAPI ThreadProc3(LPVOID lpParameter);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread[3] = { 0 };
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
// 创建事件对象
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_SETEVENT), FALSE);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_CREATETHREAD: {
// 重置事件对象
ResetEvent(g_hEvent);
hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
hThread[2] = CreateThread(NULL, 0, ThreadProc3, NULL, 0, NULL);
for (SIZE_T i = 0; i < 3; i++)
if (hThread[i] != NULL)
CloseHandle(hThread[i]);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_SETEVENT), TRUE);
break;
};
case IDC_BTN_SETEVENT: {
// 设置事件对象
SetEvent(g_hEvent);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_SETEVENT), FALSE);
break;
};
case IDCANCEL: {
// 关闭事件对象句柄
CloseHandle(g_hEvent);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
WaitForSingleObject(g_hEvent, INFINITE);
MessageBox(g_hwndDlg, TEXT("线程1成功等待到事件对象"), TEXT("提示"), MB_OK);
// 做一些工作
//SetEvent(g_hEvent);
return 0;
};
DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
WaitForSingleObject(g_hEvent, INFINITE);
MessageBox(g_hwndDlg, TEXT("线程2成功等待到事件对象"), TEXT("提示"), MB_OK);
// 做一些工作
//SetEvent(g_hEvent);
return 0;
};
DWORD WINAPI ThreadProc3(LPVOID lpParameter) {
WaitForSingleObject(g_hEvent, INFINITE);
MessageBox(g_hwndDlg, TEXT("线程3成功等待到事件对象"), TEXT("提示"), MB_OK);
// 做一些工作
//SetEvent(g_hEvent);
return 0;
};

线程间同步

原子访问

x86的不讲了,只讲x64的。以原子方式对多个线程的共享变量进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
LONG InterlockedIncrement64(
_Inout_ PLONG volatile Addend
); //递增
LONG InterlockedDecrement64(
_Inout_ PLONG volatile Addend
); //递减
LONG InterlockedExchangeAdd64(
_Inout_ PLONG volatile Addend,
_In_ LONG Value
); //自加
LONG InterlockedExchange64(
_Inout_ PLONG volatile Target,
_In_ LONG Value
); //修改
PVOID InterlockedExchangePointer(
_Inout_ PVOID volatile* Target,
_In_ PVOID Value
); //修改为指针值
LONG InterlockedCompareExchange64(
_Inout_ PLONG volatile Destination,
_In_ LONG ExChange,
_In_ LONG Comperand
); //当*Destination等于Comperand时 *Destination变为ExChange
LONG InterlockedCompareExchangePointer(
_Inout_ PVOID volatile* Destination,
_In_ PVOID Exchange,
_In_ PVOID Comperand
); //同理
LONG InterlockedAnd64(
_Inout_ PLONG volatile Destination,
_In_ LONG Value
); //与运算
LONG InterlockedOr64(
_Inout_ PLONG volatile Destination,
_In_ LONG Value
); //或运算
LONG InterlockedXor64(
_Inout_ PLONG volatile Destination,
_In_ LONG Value
); //异或运算

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <windows.h>
#include "resource.h"
// 常量定义
#define NUM 2
// 全局变量
INT g_n;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
INT WINAPI _wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread[NUM] = { 0 };
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_START: {
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), FALSE);
g_n = 10; // 创建线程执行线程函数以前把全局变量g_n赋值为10
for (int i = 0; i < NUM; i++)
hThread[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
WaitForMultipleObjects(NUM, hThread, TRUE, INFINITE);
for (int i = 0; i < NUM; i++)
if (hThread[i] != NULL)
CloseHandle(hThread[i]);
// 所有线程结束以后,把g_n的最终值显示在编辑控件中
SetDlgItemInt(hwndDlg, IDC_EDIT_NUM, g_n, TRUE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), TRUE);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
for (INT i = 1; i <= 100000000; i++) {
InterlockedIncrement((PLONG)&g_n);
InterlockedDecrement((PLONG)&g_n);
};
return 0;
};

关键段

把操作共享资源的一段代码保护起来,当一个线程正在执行操作共享资源的这段代码时,其他试图访问共享资源的线程都将被挂起,一直等前一个线程执行完,其他线程才可以执行操作共享资源的代码。

1
2
3
4
5
6
7
8
9
10
11
12
VOID WINAPI InitializeCriticalSection( //初始化关键段对象
_Out_ LPCRITICAL_SECTION lpCriticalSection
);
VOID WINAPI EnterCriticalSection( //试图进入关键段
_Inout_ LPCRITICAL_SECTION lpCriticalSection
);
VOID WINAPI LeaveCriticalSection( //离开关键段
_Inout_ LPCRITICAL_SECTION lpCriticalSection
);
VOID WINAPI DeleteCriticalSection( //释放关键段对象
_Inout_ LPCRITICAL_SECTION lpCriticalSection
);

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <windows.h>
#include "resource.h"
// 常量定义
#define NUM 2
// 全局变量
INT g_n;
CRITICAL_SECTION g_cs;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
INT WINAPI _wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread[NUM] = { 0 };
switch (uMsg) {
case WM_INITDIALOG: {
// 初始化关键段对象CRITICAL_SECTION结构
InitializeCriticalSection(&g_cs);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_START: {
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), FALSE);
g_n = 10; // 创建线程执行线程函数以前把全局变量g_n赋值为10
for (INT i = 0; i < NUM; i++)
hThread[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
WaitForMultipleObjects(NUM, hThread, TRUE, INFINITE);
for (INT i = 0; i < NUM; i++)
if (hThread[i] != NULL)
CloseHandle(hThread[i]);
// 所有线程结束以后,把g_n的最终值显示在编辑控件中
SetDlgItemInt(hwndDlg, IDC_EDIT_NUM, g_n, TRUE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), TRUE);
break;
};
case IDCANCEL: {
// 释放关键段对象
DeleteCriticalSection(&g_cs);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
for (INT i = 1; i <= 100000000; i++) {
// 进入关键段
EnterCriticalSection(&g_cs);
g_n++;
g_n--;
// 离开关键段
LeaveCriticalSection(&g_cs);
};
return 0;
};

SRW锁

跟关键段思想差不多,分为共享模式、独占模式两种。

共享模式时,可以多个进程同时读取某个资源,但不能同时获得写入锁。独占模式时无论读取还是写入都只能一个线程访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
VOID WINAPI InitializeSRWLock(
_Out_ PSRWLOCK pSRWLock
);
VOID WINAPI AcquireSRWLockShared( //共享模式
_Inout_ PSRWLOCK pSRWLock
);
VOID WINAPI ReleaseSRWLockShared(
_Inout_ PSRWLOCK pSRWLock
);
VOID WINAPI AcquireSRWLockExclusive( //独占模式
_Inout_ PSRWLOCK pSRWLock
);
VOID WINAPI ReleaseSRWLockExclusive(
_Inout_ PSRWLOCK pSRWLock
);

条件变量

例如读取线程和写入线程共用一个缓冲区,写入线程不断向缓冲区写入数据,当缓冲区已满,吸入线程释放关键段或SRW锁对象让自己进入睡眠状态,读取进程就可以获取到关键段或SRW锁对象进行读取操作,读取线程每读取一项就让缓冲区减少一项,并释放关键段或SRW锁对象让自己进入睡眠状态,唤醒写入进程获取关键段或SRW锁对象进行写入操作。总的来说,只要缓冲区未满写入线程不停生产,只要缓冲区不空读取线程不停消费。

InitializeConditionVariable

动态初始化条件变量:

1
2
3
VOID WINAPI InitializeConditionVariable(
_Out_ PCONDITION_VARIABLE pConditionVariable
)

SleepConditionVariableCS/SleepConditionVariableSRW

原子性地释放关键段/SRW锁并进入休眠状态:

1
2
3
4
5
6
7
8
9
10
11
BOOL WINAPI SleepConditionVariableCS(
_Inout_ PCONDITION_VARIABLE pConditionVariable, //条件变量
_Inout_ PCRITICAL_SECTION pCriticalSection, //关键段对象
_In_ DWORD dwMilliseconds //超时事件 单位毫秒
);
BOOL WINAPI SleepConditionVariableSRW(
_Inout_ PCONDITION_VARIABLE pConditionVariable, //条件变量
_Inout_ PSRWLOCK pSRWLock, //SRW锁对象
_In_ DWORD dwMilliseconds, //超时时间 单位毫秒
_In_ ULONG ulFlags //SRW锁访问模式
);

WakeConditionVariable/WakeAllConditionVariable

唤醒正在条件变量上睡眠地一个/所有线程并重新获取线程进入睡眠状态时释放的关键段/SRW锁对象:

1
2
3
4
5
6
VOID WINAPI WakeConditionVariable(
_Inout_ PCONDITION_VARIABLE pConditionVariable
);
VOID WINAPI WakeAllConditionVariable(
_Inout_ PCONDITION_VARIABLE pConditionVariable
);

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#include <windows.h>
#include "resource.h"
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// 常量定义
#define QUEUE_SIZE 10 // 队列大小
#define PRODUCER_SLEEP_TIME_MS 500 // 生产者线程每500毫秒以内生产一个新项目
#define CONSUMER_SLEEP_TIME_MS 2000 // 消费者线程每2000毫秒以内消费一个项目
// 全局变量
HWND g_hwndDlg;
LONG g_lBuffer[QUEUE_SIZE]; // 生产者线程和消费者线程共用的缓冲区
LONG g_lLastItemProduced; // 生产者线程所生产项目的编号
ULONG g_ulQueueStartOffset; // 项目在队列中的起始偏移
ULONG g_ulQueueCurrentSize; // 当前队列大小
CONDITION_VARIABLE g_cvBufferNotFull; // 生产者线程所等待的条件变量
CONDITION_VARIABLE g_cvBufferNotEmpty; // 消费者线程所等待的条件变量
CRITICAL_SECTION g_csBufferLock; // 关键段,用于同步对共享资源的访问
BOOL g_bStopRequested; // 主线程是否要求停止工作
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 生产者线程函数
DWORD WINAPI ProducerThreadProc(LPVOID lpParameter);
// 消费者线程函数
DWORD WINAPI ConsumerThreadProc(LPVOID lpParameter);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HANDLE hProducer1, hConsumer1, hConsumer2;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;

// 初始化关键段和条件变量
InitializeCriticalSection(&g_csBufferLock);
InitializeConditionVariable(&g_cvBufferNotEmpty);
InitializeConditionVariable(&g_cvBufferNotFull);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_START: {
// 创建1个生产者线程和2个消费者线程
hProducer1 = CreateThread(NULL, 0, ProducerThreadProc, (LPVOID)1, 0, NULL);
hConsumer1 = CreateThread(NULL, 0, ConsumerThreadProc, (LPVOID)1, 0, NULL);
hConsumer2 = CreateThread(NULL, 0, ConsumerThreadProc, (LPVOID)2, 0, NULL);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), FALSE);
break;
};
case IDC_BTN_STOP: {
EnterCriticalSection(&g_csBufferLock);
g_bStopRequested = TRUE;
LeaveCriticalSection(&g_csBufferLock);
WakeAllConditionVariable(&g_cvBufferNotFull);
WakeAllConditionVariable(&g_cvBufferNotEmpty);
if (hProducer1 != NULL) {
CloseHandle(hProducer1);
hProducer1 = NULL;
};
if (hConsumer1 != NULL) {
CloseHandle(hConsumer1);
hConsumer1 = NULL;
};
if (hConsumer2 != NULL) {
CloseHandle(hConsumer2);
hConsumer2 = NULL;
};
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_STOP), FALSE);
break;
};
};
return TRUE;
};
case WM_CLOSE: {
EndDialog(hwndDlg, 0);
DeleteCriticalSection(&g_csBufferLock);
return TRUE;
};
};
return FALSE;
};
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI ProducerThreadProc(LPVOID lpParameter) {
TCHAR szBuf[64] = { 0 };
// 传递过来的生产者线程编号
ULONG ulProducerId = (ULONG)(ULONG_PTR)lpParameter;
while (TRUE) {
// 每500毫秒以内生产一个新项目,ulItem是所生产的项目编号
Sleep(rand() % PRODUCER_SLEEP_TIME_MS);
ULONG ulItem = InterlockedIncrement(&g_lLastItemProduced);
// 获取关键段对象的所有权
EnterCriticalSection(&g_csBufferLock);
// 当队列已满的时候,释放关键段并进入睡眠状态,以便消费者线程可以消费项目以减小队列大小 只要队列未满,就继续生产新项目
while (g_ulQueueCurrentSize == QUEUE_SIZE && g_bStopRequested == FALSE)
SleepConditionVariableCS(&g_cvBufferNotFull, &g_csBufferLock, INFINITE);
// “所需的条件”已经成立(队列未满) 如果主线程要求停止工作,释放关键段对象的所有权并退出循环
if (g_bStopRequested == TRUE) {
LeaveCriticalSection(&g_csBufferLock);
break;
};
// 在队列末尾插入新项目,增加当前队列大小
g_lBuffer[(g_ulQueueStartOffset + g_ulQueueCurrentSize) % QUEUE_SIZE] = ulItem;
g_ulQueueCurrentSize++;
wsprintf(szBuf, TEXT("生产者 %u: 项目编号 %2d, 当前队列大小 %2u\r\n"), ulProducerId, ulItem, g_ulQueueCurrentSize);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_INFO), EM_SETSEL, -1, -1);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_INFO), EM_REPLACESEL, TRUE, (LPARAM)szBuf);
// 释放关键段对象的所有权
LeaveCriticalSection(&g_csBufferLock);
// 唤醒一个消费者线程以消费项目
WakeConditionVariable(&g_cvBufferNotEmpty);
};
// 生产者线程退出
wsprintf(szBuf, TEXT("生产者 %u 已经退出\r\n"), ulProducerId);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_INFO), EM_SETSEL, -1, -1);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_INFO), EM_REPLACESEL, TRUE, (LPARAM)szBuf);
return 0;
};
DWORD WINAPI ConsumerThreadProc(LPVOID lpParameter) {
TCHAR szBuf[64] = { 0 };
// 传递过来的消费者线程编号
ULONG ulConsumerId = (ULONG)(ULONG_PTR)lpParameter;
while (TRUE) {
// 获取关键段对象的所有权
EnterCriticalSection(&g_csBufferLock);
// 当队列为空的时候,释放关键段并进入睡眠状态以便生产者线程可以生产项目 只要队列不为空,就继续消费项目
while (g_ulQueueCurrentSize == 0 && g_bStopRequested == FALSE)
SleepConditionVariableCS(&g_cvBufferNotEmpty, &g_csBufferLock, INFINITE);
// “所需的条件”已经成立(队列不为空) 如果主线程要求停止工作并且当前队列大小为空,释放关键段对象的所有权并退出循环
if (g_bStopRequested == TRUE && g_ulQueueCurrentSize == 0) {
LeaveCriticalSection(&g_csBufferLock);
break;
};
// lItem是生产者线程所生产的项目编号,从队列开头开始消费项目,减小当前队列大小,增加项目在队列中的偏移
LONG lItem = g_lBuffer[g_ulQueueStartOffset];
g_ulQueueCurrentSize--;
g_ulQueueStartOffset++;
if (g_ulQueueStartOffset == QUEUE_SIZE)
g_ulQueueStartOffset = 0;
wsprintf(szBuf, TEXT("消费者 %u: 项目编号 %2d, 当前队列大小 %2u\r\n"), ulConsumerId, lItem, g_ulQueueCurrentSize);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_INFO), EM_SETSEL, -1, -1);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_INFO), EM_REPLACESEL, TRUE, (LPARAM)szBuf);
// 释放关键段对象的所有权
LeaveCriticalSection(&g_csBufferLock);
// 唤醒生产者线程
WakeConditionVariable(&g_cvBufferNotFull);
// 每2000毫秒以内消费一个项目
Sleep(rand() % CONSUMER_SLEEP_TIME_MS);
};
// 消费者线程退出
wsprintf(szBuf, TEXT("消费者 %u 已经退出\r\n"), ulConsumerId);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_INFO), EM_SETSEL, -1, -1);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_INFO), EM_REPLACESEL, TRUE, (LPARAM)szBuf);
return 0;
};

互斥量

这东西太慢了,建议用关键段。

CreateMutex

创建一个互斥量对象:

1
2
3
4
5
HANDLE WINAPI CreateMutex(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,
_In_ BOOL bInitialOwner, //初始时调用线程是否有互斥量对象所有权
_In_opt_ LPCTSTR lpName //互斥量名称
);

已有该名互斥量则返回句柄,GetLastError为ERROR_ALREAD_EXISTS。有同名其他内核对象返回NULL,GetLastError返回ERROR_INVALID_HANDLE。

ReleaseMutex

释放互斥量对象所有权:

1
2
3
BOOL WINAPI ReleaseMutex(
_In_ HANDLE hMutex
);

OpenMutex

打开一个已经存在地命名互斥量对象:

1
2
3
4
5
HANDLE WINAPI OpenMutex(
_In_ DWORD dwDesiredAccess, //互斥量访问权限
_In_ BOOL bInheritHandle, //创建新进程时是否继承返回地句柄
_In_ LPCTSTR lpName //互斥量对象名
);

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <windows.h>
#include "resource.h"
// 常量定义
#define NUM 2
// 全局变量
INT g_n;
HANDLE g_hMutex;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
INT WINAPI _wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread[NUM] = { 0 };
switch (uMsg) {
case WM_INITDIALOG: {
// 创建互斥量对象
g_hMutex = CreateMutex(NULL, FALSE, NULL);
break;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_START: {
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), FALSE);
g_n = 10; // 创建线程执行线程函数以前把全局变量g_n赋值为10
for (INT i = 0; i < NUM; i++)
hThread[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
WaitForMultipleObjects(NUM, hThread, TRUE, INFINITE);
for (INT i = 0; i < NUM; i++)
if (hThread[i] != NULL)
CloseHandle(hThread[i]);
// 所有线程结束以后,把g_n的最终值显示在编辑控件中
SetDlgItemInt(hwndDlg, IDC_EDIT_NUM, g_n, TRUE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), TRUE);
break;
};
case IDCANCEL: {
// 关闭互斥量对象句柄
CloseHandle(g_hMutex);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
for (INT i = 1; i <= 1000000; i++) {
// 等待互斥量
WaitForSingleObject(g_hMutex, INFINITE);
g_n++;
g_n--;
// 释放互斥量
ReleaseMutex(g_hMutex);
};
return 0;
};

信号量

信号量包括最大可用资源计数和当前可用资源计数。例如某个程序有3个工作线程,每个工作线程可处理一个客户端请求。则创建一个最大可用资源计数为3的信号量,初始情况下当前可用资源计数为3,有客户端请求时当前可用资源计数减一,当可用资源计数为0时,其他客户端请求只能处于等待状态。当工作线程处理完请求后释放信号量对象使当前可用资源计数值加一。

CreateSemaphore

创建信号量:

1
2
3
4
5
6
HANDLE WINAPI CreateSemaphore(
_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
_In_ LONG lInitialCount, //当前可用资源计数
_In_ LONG lMaximumCount, //最大可用资源计数
_In_opt_ LPCTSTR lpName
)

ReleaseSemaphore

释放信号量:

1
2
3
4
5
BOOL WINAPI ReleaseSemaphore(
_In_ HANDLE hSemaphore, //信号量句柄
_In_ LONG lReleaseCount, //当前可用资源计数增加量 一般1
_Out_opt_ LPLONG lpPreviousCount //返回先前当前可用资源计数值
)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <windows.h>
#include "resource.h"
// 常量定义
#define NUM 2
// 全局变量
INT g_n;
HANDLE g_hSemaphore;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
INT WINAPI _wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread[NUM] = { 0 };
switch (uMsg) {
case WM_INITDIALOG: {
// 创建信号量对象
g_hSemaphore = CreateSemaphore(NULL, 1, 1, NULL);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_START: {
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), FALSE);
g_n = 10; // 创建线程执行线程函数以前把全局变量g_n赋值为10
for (INT i = 0; i < NUM; i++)
hThread[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
WaitForMultipleObjects(NUM, hThread, TRUE, INFINITE);
for (INT i = 0; i < NUM; i++)
if (hThread[i] != NULL)
CloseHandle(hThread[i]);
// 所有线程结束以后,把g_n的最终值显示在编辑控件中
SetDlgItemInt(hwndDlg, IDC_EDIT_NUM, g_n, TRUE);
EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_START), TRUE);
break;
};
case IDCANCEL: {
// 关闭信号量对象句柄
CloseHandle(g_hSemaphore);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
for (INT i = 1; i <= 1000000; i++) {
// 等待信号量
WaitForSingleObject(g_hSemaphore, INFINITE);
g_n++;
g_n--;
// 释放信号量
ReleaseSemaphore(g_hSemaphore, 1, NULL);
};
return 0;
};

可等待计时器

可以用于某个时间执行任务,也可以用于每隔一段时间触发一次。

CreateWaitableTimer

创建一个可等待计时器对象:

1
2
3
4
5
HANDLE WINAPI CreateWaitableTimer(
_In_opt_ LPSECURITY_ATTRIBUTES lpTimerAttributes,
_In_ BOOL bManualReset, //手动重置或自动重置
_In_opt_ LPCTSTR lpTimerName
)

当手动重置寄存器被触发时,正在等待该计时器的所有线程都会变成可调度状态;当自动重置寄存器被触发时,只有一个正在等待该计时器的线程变成可调度状态。

SetWaitableTimer

触发计时器:

1
2
3
4
5
6
7
8
BOOL WINAPI SetWaitableTimer(
_In_ HANDLE hTimer,
_In_ PLARGE_INTEGER pDueTime, //触发时间
_In_ LONG lPeriod, //触发周期
_In_opt_ PTIMERAPCROUTINE pfnCompletionRoutine, //完成例程
_In_opt_ LPVOID lpArgToCompletionRoutine, //传给例程的自定义数据指针
_In_ BOOL fResume //系统挂起时是否继续触发计时器
)

其中pDueTime为正数时表示UTC绝对时间。为负数时表示相对时间,单位100纳秒。

lPeriod为正数时表示开启周期模式,直到CancelWaitableTimerSetWaitableTimer重设。设置为0表示一次性的。

CancelWaitableTimer

取消计时器:

1
2
3
BOOL WINAPI CancelWaitableTimer(
_In_ HANDLE hTimer
)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <windows.h>
#include <Commctrl.h>
#include "resource.h"
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// 全局变量
HWND g_hwndDlg;
HANDLE g_hTimer;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
INT WINAPI _wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
SYSTEMTIME st = { 0 };
FILETIME ftLocal, ftUTC;
LARGE_INTEGER li;
HANDLE hThread = NULL;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
// 创建一个自动重置可等待计时器对象
g_hTimer = CreateWaitableTimer(NULL, FALSE, NULL);
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
if (hThread != NULL)
CloseHandle(hThread);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_SET:
case IDC_BTN_RESET: {
// 自定义一个时间
/*st.wYear = 2019;
st.wMonth = 8;
st.wDay = 5;
st.wHour = 17;
st.wMinute = 45;
st.wSecond = 0;
st.wMilliseconds = 0;*/
// 获取日期时间控件的时间
SendDlgItemMessage(hwndDlg, IDC_DATETIMEPICKER, DTM_GETSYSTEMTIME, 0, (LPARAM)&st);
// 系统时间转换成FILETIME时间
SystemTimeToFileTime(&st, &ftLocal);
// 本地FILETIME时间转换成UTC的FILETIME时间
LocalFileTimeToFileTime(&ftLocal, &ftUTC);
// 不要将指向FILETIME结构的指针强制转换为LARGE_INTEGER *或__int64 *类型,
li.LowPart = ftUTC.dwLowDateTime;
li.HighPart = ftUTC.dwHighDateTime;
// 设置可等待计时器
SetWaitableTimer(g_hTimer, &li, 10 * 1000, NULL, NULL, FALSE);
break;
};
case IDC_BTN_CANCEL: {
// 取消可等待计时器
CancelWaitableTimer(g_hTimer);
break;
};
case IDCANCEL: {
// 关闭可等待计时器对象句柄
CloseHandle(g_hTimer);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
while (TRUE) {
// 等待可等待计时器
WaitForSingleObject(g_hTimer, INFINITE);
ShellExecute(NULL, TEXT("open"), TEXT("Calc.exe"), NULL, NULL, SW_SHOW);
};
return 0;
};