内核对象

一、内核对象是什么?

  • 内核对象本质上是一个结构体,但是这个结构体只能由操作系统内核部分进行访问,并且只能由它进行创建,如果用于想要操作一个内核对象,就必须通过句柄找到内核对象,并且使用相应的API进行访问。
  • 内核对象的通用操作形式:创建内核对象:CreateXXX,打开一个内核对象 OpenXXX,关闭内核对象一般会使用到 CloseHandle()。

二、内核对象的特性

  1. 全局性:内核对象是跨进程的,可以在不同进程中访问同一个对象,通常使用字符串或ID标识一个对象。
  2. 引用计数:每一个内核对象都有一个引用计数,当创建或者打开一个内核对象的时候,引用计数会+1,当关闭一个内核对象的时候,引用计数会-1,当引用计数为0时,内核对象会被销毁。
  3. 安全性:大多数内核对象在创建的时候,都需要设置一个安全属性,安全属性描述了哪些用户可以以什么样的方式访问内核对象,目前通常使用NULL来填充这个字段。
  4. 句柄表:每一个进程都有一个句柄表,相同的内核对象在不同的进程中,可能句柄值是不一样的,通常句柄值是句柄表的下标左移两位(4的倍数),句柄表描述了内核对象所在的位置以及访问需要用到的权限。一个进程退出的时候,会主动将句柄表中的内核对象计数-1
  • 进程A的句柄表
句柄值 内核对象的地址 打开的方式
[1] * 4 文件 0x80001000 创建了文件 test1.txt
[2] * 4 文件0x80002000 打开了 test2.txt
  • 进程B的句柄表
句柄值 内核对象的地址 打开的方式
[1] * 4 文件 0x80002000 创建了文件 test2.txt
[2] * 4 文件 0x80001000 打开了 test1.txt
[3] * 4 文件 0x80001000 打开了 test1.txt

三、 内核对象的跨进程访问

  1. 由父进程继承给子进程
  2. 通过内核对象的ID或字符串名称打开内核对象
  3. 使用 DuplicateHandle() 函数在进程间传递内核对象

四、 在句柄表中添加内核对象的方式

  1. 创建一个内核对象
  2. 打开一个内核对象
  3. 从父进程继承内核对象(子进程本身并不知道自己继承了父进程的内核对象)
  4. 使用 DuplicateHandle() 拷贝内核对象

进程和模块

一、 什么是进程?

进程可以看作是一个运行中的程序,应该由一个可执行程序(.exe)产生。一个进程最少包含了一个进程内核对象、一个线程内核对象(执行代码)、一块虚拟地址空间(4GB)、需要用到的数据和代码(模块)

二、什么是模块?

windows下的可执行文件(.dll .exe)通常被称作模块,用于提供必须的数据以及代码。

在 VS 中: 程序断下来后 -> 菜单 -> 调试 -> 窗口 -> 模块

三、 退出进程的方式

  1. 通过main()\WinMain() 主动的进程退出
  2. 通过 ExitProcess 函数退出
  3. 通过 TerminateProcess 函数强制结束

进程操作

创建进程

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
#include <iostream>
#include <windows.h>

// 创建一个进程

int main(int argc, char*argv[])
{
// 用于存放被创建进程的进程句柄、主线程句柄和pid以及tid
PROCESS_INFORMATION ProcessInfomation = { 0 };

// 用于设置进程的启动信息(结构体的首字段如果是 cb 或者 dwSize 就初始化)
STARTUPINFO StartupInfo = { sizeof(STARTUPINFO) };

// 创建进程的函数
CreateProcess(
L"C:\\Windows\\System32\\notepad.exe", // 参数1: 需要创建的进程对应的exe文件
NULL, // 参数2: 指定应用程序使用到的参数
NULL, NULL, // 参数3,参数4: 进程内核对象和线程内核对象的安全属性
FALSE, // 参数5: 子进程是否继承父进程的句柄,只有允许继承的句柄才会被继承
NULL, // 参数6: 创建标志 CREATE_NEW_CONSOLE,使用两个控制台
NULL, // 参数7: 环境变量
NULL, // 参数8: 工作路径
&StartupInfo,
&ProcessInfomation
);

// 通常需要关闭句柄,这里关闭不会销毁内核对象
CloseHandle(ProcessInfomation.hThread);
CloseHandle(ProcessInfomation.hProcess);

// 还可以使用 WinExec ShellExecute system 等创建进程
system("start notepad.exe");

return 0;
}

操作进程

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
#include <iostream>
#include <windows.h>

int main()
{
// 操作进程需要用到进程的句柄

// 可以通过 FindWindow 找到窗口句柄
HWND hWnd = FindWindow(NULL, L"无标题 - 记事本");

// 可以通过窗口句柄获取到进程ID
DWORD Pid = 0;
GetWindowThreadProcessId(hWnd, &Pid);

// 可以使用 PID 获取到进程的句柄
HANDLE Process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, Pid);

// 通过句柄来结束一个指定进程
TerminateProcess(Process, -1);
CloseHandle(Process);

// 结束当前的进程,不推荐使用
ExitProcess(-1);

return 0;
}

遍历进程

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
#include <iostream>
#include <string>
#include <windows.h>
using namespace std;

// 进程快照 + EnumProcess

// 1. 包含需要用到的头文件
#include <TlHelp32.h>

// 什么是快照? 将调用函数的【那一刻】操作系统的进程状态记录下来

int main()
{
wstring buffer;

// 2. 创建一个快照用于遍历进程,参数2可以留空
HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

// 3. 创建一个用于保存进程信息的结构体
PROCESSENTRY32 ProcessInfo = { sizeof(PROCESSENTRY32) };

// 4. 尝试遍历第一个进程的信息,成功就继续,失败就跳过
if (Process32First(Snapshot, &ProcessInfo))
{
do {
// 5. 输出遍历到的进程的信息: 使用 %ls 可以输出宽字符
buffer += ProcessInfo.szExeFile;
buffer += '\n';

/*
typedef struct tagPROCESSENTRY32W
{
DWORD th32ProcessID; // 进程的ID
DWORD cntThreads; // 拥有的线程总数
DWORD th32ParentProcessID; // 所属进程的ID
DWORD dwFlags; // 标志
WCHAR szExeFile[MAX_PATH]; // 进程的名称
} PROCESSENTRY32W;
*/

// 获取进程对应exe所在的路径
{
// 5.5.1 用于保存进程路径的缓冲区和大小
DWORD PathSize = MAX_PATH;
WCHAR ImagePath[MAX_PATH] = { 0 };

// 5.5.2 通过指定的权限获取进程句柄
HANDLE Process = OpenProcess(PROCESS_QUERY_INFORMATION,
FALSE, ProcessInfo.th32ProcessID);

// 5.5.3 通过API查询到进程对应的Exe的路径,获取不到是因为权限不够
QueryFullProcessImageName(Process, 0, ImagePath, &PathSize);

// 5.5.4 关闭句柄并打印数据
CloseHandle(Process);
// printf("\t%ls\n", ImagePath);
buffer += '\t';
buffer += ImagePath;
buffer += '\n';
}

// 6. 尝试遍历进程快照内的下一个进程
} while (Process32Next(Snapshot, &ProcessInfo));
}

// 如果进行快速的输出: 减少输出函数的调用,
// 一般会对想要输出的内容进行拼接,再统一的调用一次函数
printf("%ls\n", buffer.c_str());
return 0;
}

遍历模块

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
#include <iostream>
#include <windows.h>

// 1. 包含需要用到的头文件
#include <TlHelp32.h>

int main()
{
// 2. 创建一个快照用于遍历模块,参数2是指定的进程ID
// [记得修改第一个参数为 TH32CS_SNAPMODULE]
HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 10536);

// 3. 创建一个用于保存模块信息的结构体
MODULEENTRY32 ModuleInfo = { sizeof(MODULEENTRY32) };

// 4. 尝试遍历第一个模块的信息,成功就继续,失败就跳过
if (Module32First(Snapshot, &ModuleInfo))
{
do {
// 5. 输出遍历到的模块的信息
printf("%ls\n", ModuleInfo.szExePath);

/*
typedef struct tagMODULEENTRY32W
{
DWORD th32ProcessID; // 所属进程ID
BYTE * modBaseAddr; // 模块的加载基地址
DWORD modBaseSize; // 模块的大小
HMODULE hModule; // 模块的句柄(加载基址)
WCHAR szModule[MAX_MODULE_NAME32 + 1]; // 模块名
WCHAR szExePath[MAX_PATH]; // 所在路径
} MODULEENTRY32W;
*/

// 6. 尝试遍历模块快照内的下一个模块
} while (Module32Next(Snapshot, &ModuleInfo));
}

return 0;
}

文件操作

无句柄

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
#include <iostream>
#include <windows.h>

int main()
{
// 1. 删除指定文件和目录: 文件路径,不能乱用
// DeleteFile(L"E:\\123.txt");
RemoveDirectory(L"E:\\123");

// 2. 复制文件到某一个地方,最后一个参数为FALSE表示覆盖已有文件
// CopyFile(L"E:\\123.txt", L"E:\\123\\456.txt", FALSE);

// 3. 移动文件(重命名)
// MoveFile(L"E:\\123.txt", L"E:\\456.txt");

// 4. 获取文件的信息(属性\时间\大小)
// 使用非 Ex 版本可以只获取文件的属性
WIN32_FILE_ATTRIBUTE_DATA FileInfo = { 0 };
GetFileAttributesEx(L"E:\\新建文件夹",
GetFileExInfoStandard, &FileInfo);

// FileTimeToLocalFileTime + FileTimeToSystemTime

// 获取的时间是格林威治时间,需要转换成本地时间
FILETIME LocalTime = { 0 };
FileTimeToLocalFileTime(&FileInfo.ftCreationTime, &LocalTime);
// 转换时间: 将文件时间转换为系统时间
SYSTEMTIME System = { 0 };
FileTimeToSystemTime(&LocalTime, &System);
/////////////////////////////////////////////

// 判断一个文件是否拥有某一种属性, dwFileAttributes 每一位代表了
// 一种属性,如果拥有这种属性,对应的就为 1
if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
printf("目录 ");
else
printf("文件 ");
printf("%s\n", FileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN ? "隐藏" : "显示");

return 0;
}

有句柄

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
#include <iostream>
#include <windows.h>

// 使用句柄操作的一些方式

int main()
{
// 1. 创建一个文件
HANDLE Handle = CreateFileA(
"E:\\abc.txt", // 文件的位置
GENERIC_ALL, // 文件的属性
NULL, // 共享方式 FILE_SHARE_READ
NULL, // 安全描述符
CREATE_ALWAYS, // 不管是否存在都创建
FILE_ATTRIBUTE_NORMAL, // 默认属性
NULL);

// 2. 打开一个文件,文件打开失败会返回 INVALID_HANDLE_VALUE(-1)
HANDLE Handle = CreateFileA(
"E:\\abc.txt", // 文件的位置
GENERIC_ALL, // 文件的属性
NULL, // 共享方式
NULL, // 安全描述符
OPEN_EXISTING, // 存在才打开
FILE_ATTRIBUTE_NORMAL, // 默认属性
NULL);

// 写入数据
DWORD RealWrite = 0;
// 参数1: 想要操作的文件句柄
// 参数2: 想要写入的数据所在的位置
// 参数3: 想要写入数据的长度
// 参数4: 实际写入的数据的长度
WriteFile(Handle, "123456", 3, &RealWrite, NULL);

// 设置文件指针到起始位置
SetFilePointer(Handle, 0, NULL, FILE_BEGIN);
DWORD Current = SetFilePointer(Handle, 0, NULL, FILE_CURRENT);

// 读取数据
// 参数1: 想要操作的文件句柄
// 参数2: 想要将数据读取到哪里
// 参数3: 想要读取多少字节的数据
// 参数4: 实际读取的数据的长度
DWORD RealRead = 0;
CHAR Buffer[10] = { 0 };
ReadFile(Handle, Buffer, 3, &RealRead, NULL);

// 设置文件的结束位置
SetFilePointer(Handle, 2, NULL, FILE_BEGIN);
SetEndOfFile(Handle);

// 获取文件的大小,参数2是文件大小的高位,不适用可以填充NULL
LARGE_INTEGER size;
size.LowPart = GetFileSize(Handle, (LPDWORD)&size.HighPart);
// size.QuadPart 这个就是文件大小

return 0;
}

文件遍历

例子1
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
#include <iostream>
#include <windows.h>

// 递归遍历目录下的所有文件
void EnumFile(LPCSTR DirPath, int level)
{
// 1. 拼接字符串: 目录 + \\*,* 是通配符表示要查找任何类型的文件
CHAR Path[MAX_PATH] = { 0 };
sprintf(Path, "%s\\*", DirPath); // strcat(Path, "\\*");


// 2. 保存文件信息的结构体
WIN32_FIND_DATAA FindData = { 0 };

// 3. 尝试遍历第一个文件
HANDLE FindHandle = FindFirstFileA(Path, &FindData);

// 4. 如果遍历到了文件,就继续,否则跳过
if (FindHandle != INVALID_HANDLE_VALUE)
{
do {
// 判断这个文件是不是目录
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// . 当前目录 .. 上级目录 排除这两个
if (strcmp(FindData.cFileName, ".") && strcmp(FindData.cFileName, ".."))
{
// * 表示的是一个接受的参数,这个参数用于设置宽度
printf("%*s目录: %s\n", level, "", FindData.cFileName);
// 拼接出目录的名字 所在目录 + 当前名字
CHAR NextPath[MAX_PATH] = { 0 };
sprintf(NextPath, "%s\\%s", DirPath, FindData.cFileName);
EnumFile(NextPath, level + 1);
}
}
else
{
printf("%*s文件: %s\n", level, "", FindData.cFileName);
}
} while (FindNextFileA(FindHandle, &FindData));
}

// 关闭句柄,注意没有用 CloseHandle
FindClose(FindHandle);
}

int main()
{
EnumFile("D:\\", 1);

return 0;
}
例子2
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
//文件遍历
void CMyDlg2::EnumFile(CString filePath)
{//每次遍历都得置0
i = 0;
j = 0;
dwConstSize1 = 0;
dwConstSize2 = 0;
CString cs;
// 拼接完整路径
CString fullPath = filePath + L"\\*";
// 查找第一个文件
WIN32_FIND_DATA fileData = {};
HANDLE hFile = FindFirstFile(fullPath, &fileData);
if (hFile != INVALID_HANDLE_VALUE)
{
do
{
// 输出文件的信息,
// 过滤两个文件夹……
if (wcscmp(fileData.cFileName, L".") == 0 ||
wcscmp(fileData.cFileName, L"..") == 0)
continue;

// 如果找到的是目录,递归遍历目录中的其他文件
if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
EnumFile(filePath + L"\\" + fileData.cFileName);
continue;
}
m_FileList.InsertItem(i, filePath + L"\\" + fileData.cFileName);
// 获取文件大小
DWORD dwSize = (fileData.nFileSizeHigh * (MAXDWORD + 1))
+ fileData.nFileSizeLow;
// 转换成字符串格式

cs.Format(L"%d kb", dwSize/1024);
m_FileList.SetItemText(i, 1, cs);
// 筛选处指定后缀名的文件
if (wcscmp(L".exe", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".obj", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".tlog", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".idb", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".pdb", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".pch", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".res", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".ilk", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".sdf", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".ipch", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".log", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".lastbuildstate", PathFindExtension(fileData.cFileName)) == 0
)
{
CString csPath;//保存路径到vector中
csPath.Format(L"%s\\%s" , filePath,fileData.cFileName);

path.push_back(csPath);//将路径加入vector
m_FileList2.InsertItem(i, filePath + L"\\" + fileData.cFileName);
m_FileList2.SetItemText(i, 1, cs);

dwConstSize2 += dwSize / 1024;
j ++;
}
dwConstSize1 += dwSize / 1024;
i++;
// 继续遍历下一个文件
} while (FindNextFile(hFile, &fileData));
}
}

进程间通讯

WM_COPYDATA

使用WM_COPYDATA进行通信

使用 WM_COPYDATA 要求接受端必须是一个窗口程序

发送端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <windows.h>

int main()
{
// 查找窗口句柄
HWND hWnd = FindWindowA(NULL, "Window1");

// 设置实际要传输的数据
COPYDATASTRUCT Data = { 0 };
Data.cbData = 6; // 传输的数据长度
Data.lpData = (LPVOID)"hello"; // 传输的数据
Data.dwData = 0x12345678; // 通常用于表示消息的类型

// 向指定窗口发送数据
SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&Data);

return 0;
}
接收端
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
#include <windows.h>

// 缺陷:必须是窗口程序

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
// 处理 WM_COPYDATA 消息
case WM_COPYDATA:
{
// 获取发送过来的消息
COPYDATASTRUCT* CopyData = (COPYDATASTRUCT*)lParam;
MessageBoxA(NULL, (LPCSTR)CopyData->lpData,
(LPCSTR)CopyData->lpData, MB_OK);
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}


int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
// 设置窗口类
WNDCLASS WndClass = { 0 };
WndClass.lpszMenuName = NULL;
WndClass.lpfnWndProc = WndProc;
WndClass.hInstance = hInstance;
WndClass.lpszClassName = L"Window";
WndClass.style = CS_HREDRAW | CS_VREDRAW;
WndClass.cbClsExtra = WndClass.cbWndExtra = 0;
WndClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

// 注册窗口类
RegisterClass(&WndClass);

HWND hWnd = CreateWindowW(L"Window", L"Window1", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

// 显示更新窗口
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

// 主消息循环:
MSG msg = { 0 };
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}

邮槽mailslot

使用mailslot进行通信

邮槽只能用于单向通信,但是可以在局域网内进行通信

发送端
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
#include <iostream>
#include <windows.h>

int main()
{
// 1. 打开邮槽对象
HANDLE hFile = CreateFile(
L"\\\\.\\mailslot\\Sample", // 邮槽名称
GENERIC_WRITE, // 读写属性
FILE_SHARE_READ, // 共享属性
NULL, // 安全属性
OPEN_EXISTING, // 打开方式
FILE_ATTRIBUTE_NORMAL, // 标志位
NULL); // 文件模板(默认留空)

// 2. 向mailslot写入
DWORD dwWritten = 0;
LPCSTR lpMessage = "邮槽测试消息!";
DWORD dwMegLen = strlen(lpMessage) + sizeof(CHAR);
WriteFile(hFile, lpMessage, dwMegLen, &dwWritten, NULL);

// 3. 结束
printf("已经向邮槽写入信息!\n");
CloseHandle(hFile);

return 0;
}
接收端
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
#include <iostream>
#include <windows.h>

int main()
{
// 1. 创建邮槽对象
HANDLE hSlot = CreateMailslot(
// \\\\.\\mailslot\\ + 指定的名字
L"\\\\.\\mailslot\\Sample", // 邮槽名
0, // 不限制消息大小
MAILSLOT_WAIT_FOREVER, // 无超时
NULL); // 安全属性

// 2. 循环读取邮槽信息
while (true) {

// 2.1 获取邮槽消息数量
DWORD dwMsgCount = 0, dwMsgSize = 0;

// 2.2 获取邮槽信息
GetMailslotInfo(
hSlot, // 邮槽句柄
NULL, // 无最大消息限制
&dwMsgSize, // 下一条消息的大小
&dwMsgCount, // 消息的数量
NULL); // 无时限

// 2.3 当邮槽内没有消息
if (dwMsgSize == MAILSLOT_NO_MESSAGE) {
Sleep(2000); // 休眠2s
continue;
}

// 2.4 循环获取全部消息(有可能不只一条)
while (dwMsgCount)
{
// 根据目标发送的消息长度申请空间
PBYTE lpBuffer = new BYTE[dwMsgSize]{ 0 };

// 读取邮槽中的信息
DWORD dwRet = 0;
if (!ReadFile(hSlot, lpBuffer, dwMsgSize, &dwRet, NULL))
{
printf("ReadFile函数执行失败,错误码:%d.\n", GetLastError());
delete[] lpBuffer;
return 0;
}

// 显示信息
printf("邮槽的内容: %s\n", lpBuffer);

// 计算剩余的消息数, 更新循环的条件
GetMailslotInfo(hSlot, (LPDWORD)NULL, &dwMsgSize, &dwMsgCount, nullptr);
delete[] lpBuffer;
}

}
return 0;
}

线程操作

创建线程

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
#include <iostream>
#include <windows.h>

// 线程内核对象,线程栈帧(局部变量、参数),代码的起始位置

// 线程回调函数的原型
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
// 获取到传入的参数
LPCSTR Str = (LPCSTR)lpThreadParameter;

// 循环打印字符串
while (TRUE)
{
printf("WorkerThread: %s\n", Str);
Sleep(1000);
}

return 0;
}

int main()
{
// 创建线程
HANDLE Thread = CreateThread(
NULL, // 线程的安全属性
NULL, // 默认栈的大小(局部变量、参数、返回地址)
WorkerThread, // 线程代码的起始位置
(LPVOID)"15pb", // 线程函数的参数,如果传递的是地址,那么需要保证这块内存地址的生命周期(不能是局部变量)
NULL, // 创建标志
NULL); // 传出的线程ID

// 主函数内也打印数据
while (TRUE)
{
printf("MainThread\n");
Sleep(1000);
}

// process.h
// _beginthreadex(); // 调用 CreateThread();
// _endthreadex(-1); // 调用 ExitThread(-1);

WaitForSingleObject(Thread, INFINITY);

return 0;
}

等待线程

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
#include <iostream>
#include <windows.h>

// 线程回调函数的原型
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
// 获取到传入的参数
LPCSTR Str = (LPCSTR)lpThreadParameter;

// 循环打印字符串
for (int i = 0; i < 100; ++i)
{
printf("WorkerThread: [%d]%s\n", i, Str);
}

return 0;
}

int main()
{
// 【主线程一旦退出,那么所有的子线程也会退出】

// 创建线程,返回值就是内核对象句柄
HANDLE Thread = CreateThread(
NULL, // 线程的安全属性
NULL, // 默认栈的大小(局部变量、参数、返回地址)
WorkerThread, // 线程代码的起始位置
(LPVOID)"15pb", // 线程函数的参数
NULL, // 创建标志
NULL); // 传出的线程ID

// 主函数内也打印数据
printf("MainThread\n");

// 线程是可以等待的,因为线程内核对象有两种状态
// 当线程在执行时:属于非激发态(无信号状态)
// 当线程执行完毕时:属于激发态

// 等待内核对象变成激发态,如果是激发态就继续执行
// 否则阻塞当前线程,如果超出设置等待的时间,同样继续执行
if (Thread != NULL) WaitForSingleObject(Thread, INFINITE);

return 0;
}

遍历线程

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
#include <iostream>
#include <windows.h>

// 1. 包含对应的头文件
#include <TlHelp32.h>

int main()
{
// 2. 创建快照用于遍历线程,参数 1 为TH32CS_SNAPTHREAD, 参数 2 没有意义
// 在实际的使用中,参数2不管是什么,遍历到的都是所有的线程
HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);

// 3. 初始化结构体用于保存遍历到的线程的数据
THREADENTRY32 ThreadInfo = { sizeof(THREADENTRY32) };

// 4. 尝试遍历快照中的第一个线程
if (Thread32First(Snapshot, &ThreadInfo))
{
// 5. 输出对应的信息
do {
// [PID 和 TID 共用同一套数据]

// 5.1 筛选出对应进程的所有线程
if (ThreadInfo.th32OwnerProcessID == 4936)
{
printf("TID: %d\n", ThreadInfo.th32ThreadID);
}

// 6. 遍历下一个线程
} while (Thread32Next(Snapshot, &ThreadInfo));
}

return 0;
}

挂起恢复

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
#include <iostream>
#include <windows.h>

// 1. 包含对应的头文件
#include <TlHelp32.h>

int main()
{
// 2. 创建快照用于遍历线程,参数 1 为TH32CS_SNAPTHREAD, 参数 2 没有意义
// 在实际的使用中,参数2不管是什么,遍历到的都是所有的线程
HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);

// 3. 初始化结构体用于保存遍历到的线程的数据
THREADENTRY32 ThreadInfo = { sizeof(THREADENTRY32) };

// 4. 尝试遍历快照中的第一个线程
if (Thread32First(Snapshot, &ThreadInfo))
{
// 5. 输出对应的信息
do {
// [PID 和 TID 共用同一套数据]

// 5.1 筛选出对应进程的所有线程
if (ThreadInfo.th32OwnerProcessID == 5028)
{
// 打开线程内核对象
HANDLE Thread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, ThreadInfo.th32ThreadID);

// 挂起线程,每挂起一次线程,挂起计数就+1
// SuspendThread(Thread);

// 恢复线程,每恢复一次,挂起计数-1,当挂起计数为0时,程序就可以运行了
ResumeThread(Thread);

// 结束线程(需要结束线程的权限)
TerminateThread(Thread, -1);

// 关闭线程内核独享
CloseHandle(Thread);
}

// 6. 遍历下一个线程
} while (Thread32Next(Snapshot, &ThreadInfo));
}

return 0;
}

使用伪句柄

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 <iostream>
#include <windows.h>

// 编写函数打印线程的创建时间
void GetThreadCreateTime(HANDLE Thread)
{
// 创建对应的结构保存时间
FILETIME CreateTime = { 0 }, UserTime = { 0 };
FILETIME KernelTime = { 0 }, ExitTime = { 0 };

// 使用函数获取到线程的创建时间
GetThreadTimes(Thread, &CreateTime,
&ExitTime, &KernelTime, &UserTime);

// 将时间转换为本地的系统时间
FILETIME LocalTime = { 0 };
FileTimeToLocalFileTime(&CreateTime, &LocalTime);
SYSTEMTIME SystemTime = { 0 };
FileTimeToSystemTime(&LocalTime, &SystemTime);

// 输出线程的创建时间
printf("%d 年 %d 月 %d 日 %d 时 %d 分 %d 秒\n",
SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay,
SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond);
}

// 工作线程
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
// 获取传入的主线程伪句柄
HANDLE MainThread = (HANDLE)lpThreadParameter;

// 在子线程中通过主线程的【伪句柄】打印它的创建时间
GetThreadCreateTime(MainThread);

// 伪句柄始终表示的所在的线程,所以使用伪句柄打印不了其它线程的信息

return 0;
}

int main()
{
// 获取当前线程的伪句柄,线程的伪句柄始终是-2,进程伪句柄是-1
HANDLE MainThread = GetCurrentThread();

// 在主线程中先打印出主线程的创建时间
GetThreadCreateTime(MainThread);

// 等待两秒钟再创建子线程
Sleep(2000);

// 将主线程的伪句柄传入给子线程并打印伪句柄对应线程的创建时间
// 想要在工作线程中打印主线程的创建时间,需要传入主线程的句柄
HANDLE Thread = CreateThread(NULL, NULL, WorkerThread,
(LPVOID)MainThread, NULL, NULL);

// 等待子线程执行结束
WaitForSingleObject(Thread, INFINITE);

return 0;
}

伪句柄转换

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
#include <iostream>
#include <windows.h>

// 编写函数打印线程的创建时间
void GetThreadCreateTime(HANDLE Thread)
{
// 创建对应的结构保存时间
FILETIME CreateTime = { 0 }, UserTime = { 0 };
FILETIME KernelTime = { 0 }, ExitTime = { 0 };

// 使用函数获取到线程的创建时间
GetThreadTimes(Thread, &CreateTime,
&ExitTime, &KernelTime, &UserTime);

// 将时间转换为本地的系统时间
FILETIME LocalTime = { 0 };
FileTimeToLocalFileTime(&CreateTime, &LocalTime);
SYSTEMTIME SystemTime = { 0 };
FileTimeToSystemTime(&LocalTime, &SystemTime);

// 输出线程的创建时间
printf("%d 年 %d 月 %d 日 %d 时 %d 分 %d 秒\n",
SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay,
SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond);
}

// 工作线程
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
// 获取传入的主线程伪句柄
HANDLE MainThread = (HANDLE)lpThreadParameter;

// 在子线程中通过主线程的【句柄】打印它的创建时间
GetThreadCreateTime(MainThread);

return 0;
}

int main()
{
// 获取当前线程的伪句柄,线程的伪句柄始终是-2,进程伪句柄是-1
HANDLE MainThread = GetCurrentThread();

// 在主线程中先打印出主线程的创建时间
GetThreadCreateTime(MainThread);

// 将伪句柄转换为真实的句柄
HANDLE RealHandle = NULL;
DuplicateHandle(
GetCurrentProcess(), // 拥有源句柄的进程句柄
GetCurrentThread(), // 需要转换的句柄(主线程伪句柄)
GetCurrentProcess(), // 想要将转换出的句柄放到哪个进程
&RealHandle, // 用于保存新句柄
0, // 安全访问级别
false, // 是否可以被子进程继承
DUPLICATE_SAME_ACCESS); // 转换选项

// 等待两秒钟再创建子线程
Sleep(2000);

// 将主线程的[句柄]传入给子线程并打印句柄对应线程的创建时间
HANDLE Thread = CreateThread(NULL, NULL, WorkerThread,
(LPVOID)RealHandle, NULL, NULL);

// 等待子线程执行结束
WaitForSingleObject(Thread, INFINITE);

return 0;
}

线程环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <windows.h>

int main()
{
// 获取线程上下文
CONTEXT stcCxt = { CONTEXT_FULL };
if (!GetThreadContext(GetCurrentThread(), &stcCxt))
return false;

// 通过修改stcCxt来修改寄存器...

// 设置进程上下文
if (!SetThreadContext(GetCurrentThread(), &stcCxt))
return false;

return 0;
}

线程同步

多线程问题

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
#include <stdio.h>
#include <windows.h>

// 全局变量,同时被多个线程访问
int Number = 0;

// 同步: 多个线程需要按照某种顺序执行
// 互斥: 多个线程操作(读写)同一个资源,不在意顺序

// 异步: 执行一件事情的时候,可以做其他事情,通常依赖函数[无论有没有完成都先返回] _kbhit()
// 同步: 执行一件事情的时候,必须等他完成(阻塞,等待函数的返回值)

// 线程函数1
DWORD WINAPI WorkerThreadA(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
Number++;
}

return 0;
}

// 线程函数2
DWORD WINAPI WorkerThreadB(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
Number++;
}

return 0;
}

int main()
{
HANDLE hThread1 = CreateThread(NULL, NULL, WorkerThreadA, NULL, NULL, NULL);
HANDLE hThread2 = CreateThread(NULL, NULL, WorkerThreadB, NULL, NULL, NULL);

// 等待两个线程同时执行结束
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);

// 输出全局变量
printf("%d", Number);

return 0;
}

// Number++ 一条指令被转换成了3条汇编指令(非原子操作)

// mov eax, dword ptr[Number] [0]: Number(0)
// add eax, 1 [0]: Number(0) eax(1)
// mov dword ptr[Number],eax [0]: Number(1)

// mov eax, dword ptr[Number] [1]: Number(1)
// add eax, 1 [1]: Number(1) eax(2)
// mov dword ptr[Number],eax [1]: Number(2)

// mov eax, dword ptr[Number] [0]: Number(2)
// add eax, 1 [0]: Number(2) eax(3) -----------

// mov eax, dword ptr[Number] [1]: Number(2)
// add eax, 1 [1]: Number(2) eax(3)
// mov dword ptr[Number],eax [1]: Number(3)

// mov eax, dword ptr[Number] [0]: Number(3)
// add eax, 1 [1]: Number(3) eax(4)
// mov dword ptr[Number],eax [1]: Number(4)

// mov dword ptr[Number],eax [0]: Number(3) -----------

原子操作

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 <stdio.h>
#include <windows.h>

// 原子操作: 将对整数进行的简单运算转换成原子操作
// 缺点: 只能保护整数,不能保护一段代码
// 相关函数: InterlockedXXXX()

// 全局变量,同时被多个线程访问
long Number = 0;

// 线程函数1
DWORD WINAPI WorkerThreadA(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
// 原子操作: 将传入的数值自增
InterlockedIncrement(&Number);

// 实际上将运算转换成了一条原子操作的汇编
// lock inc dword ptr[Number]
}

return 0;
}

// 线程函数2
DWORD WINAPI WorkerThreadB(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
InterlockedIncrement(&Number);
}

return 0;
}

int main()
{
HANDLE hThread1 = CreateThread(NULL, NULL, WorkerThreadA, NULL, NULL, NULL);
HANDLE hThread2 = CreateThread(NULL, NULL, WorkerThreadB, NULL, NULL, NULL);

// 等待两个线程同时执行结束
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);

return 0;
}

临界区

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
#include <stdio.h>
#include <windows.h>

// 临界区: 可以保护一段代码
// 特点: 有线程拥有者的概念,拥有者线程可以无限次进入当前代码块
// 缺点: 如果线程崩溃,会导致死锁。
// 相关函数:
// 初始化: InitializeCriticalSection
// 删除临界区: DeleteCriticalSection
// 开始保护 EnterCriticalSection
// 结束保护 LeaveCriticalSection

// 全局变量,同时被多个线程访问
long Number = 0;

// 临界区结构体
CRITICAL_SECTION CriticalSection = { 0 };

// 线程函数1
DWORD WINAPI WorkerThreadA(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
// 当受保护的代码已经被执行时 LockCount = -2
// 并且 OwningThread 保存的时执行执行这段代码的线程

// 表示需要保护的代码的开始部分
EnterCriticalSection(&CriticalSection);
Number++;
// 需要保护的代码的结束部分
LeaveCriticalSection(&CriticalSection);
}

return 0;
}

// 线程函数2
DWORD WINAPI WorkerThreadB(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
// 表示需要保护的代码的开始部分
EnterCriticalSection(&CriticalSection);
Number++;
// 需要保护的代码的结束部分
LeaveCriticalSection(&CriticalSection);
}

return 0;
}

int main()
{
// 初始化临界区
InitializeCriticalSection(&CriticalSection);

HANDLE hThread1 = CreateThread(NULL, NULL, WorkerThreadA, NULL, NULL, NULL);
HANDLE hThread2 = CreateThread(NULL, NULL, WorkerThreadB, NULL, NULL, NULL);

// 等待两个线程同时执行结束
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);

// 销毁临界区
DeleteCriticalSection(&CriticalSection);

return 0;
}

等待函数

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
#include <iostream>
#include <windows.h>

// 线程 A
DWORD WINAPI WorkerThreadA(LPVOID lpThreadParameter)
{
// 输出 0 ~ 99
for (int i = 0; i < 1000; ++i)
{
printf("WorkerThreadAAA: %d\n", i);
}

return 0;
}

// 线程 B
DWORD WINAPI WorkerThreadB(LPVOID lpThreadParameter)
{
// 输出 0 ~ 199
for (int i = 0; i < 200; ++i)
{
printf("WorkerThreadBBB: %d\n", i);
}

return 0;
}

int main()
{
HANDLE ThreadHandles[2] = { 0 };

// 创建两个线程
ThreadHandles[0] = CreateThread(NULL, NULL,
WorkerThreadA, NULL, NULL, NULL);
ThreadHandles[1] = CreateThread(NULL, NULL,
WorkerThreadB, NULL, NULL, NULL);

// 等待两个线程退出
// WaitForSingleObject(ThreadHandles[0], INFINITE);
// WaitForSingleObject(ThreadHandles[1], INFINITE);

// 等待多个内核对象
// 1. 需要等待多少个内核对象
// 2. 内核对象句柄的数组
// 3. 是否等待所有内核对象变成有信号的
// 4. 等待的时长,单位是毫秒
// WaitForMultipleObjects(2, ThreadHandles, TRUE, INFINITE);

// 有任何一个执行完毕,另外一个就不执行了
WaitForMultipleObjects(2, ThreadHandles, FALSE, INFINITE);

// 等待函数的副作用!!!!!!!!! 将有信号状态改为无信号状态
// 对于信号量来说,就是将信号的个数 -1

return 0;
}

互斥体

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
#include <stdio.h>
#include <windows.h>

// 互斥体: 内核对象,有线程拥有者概念,线程崩溃会自动解锁
// 缺点: 执行速度慢,因为是内核对象
// 相关函数:
// 创建\打开: Create\OpenMutex
// 开始保护: WaitForSingleObject
// 结束保护: ReleaseMutex


// 1. 创建一个互斥体内核对象
// 参数1: 安全属性
// 参数2: 是否可以被继承
// 参数3: 名字,不取名字就是NULL
HANDLE Mutex = CreateMutex(NULL, FALSE, L"Mutex");

// 全局变量,同时被多个线程访问
long Number = 0;

// 线程函数1
DWORD WINAPI WorkerThreadA(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
// 当一个互斥体对象被创建的时候,处于有信号状态,可被等待
// 副作用: 将互斥体的拥有者设置为本线程,并修改状态为无信号
WaitForSingleObject(Mutex, INFINITE);
Number++;
// 重新将互斥体变成有信号的状态
ReleaseMutex(Mutex);
}

return 0;
}

// 线程函数2
DWORD WINAPI WorkerThreadB(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(Mutex, INFINITE);
Number++;
ReleaseMutex(Mutex);
}

return 0;
}

int main()
{
HANDLE hThread1 = CreateThread(NULL, NULL, WorkerThreadA, NULL, NULL, NULL);
HANDLE hThread2 = CreateThread(NULL, NULL, WorkerThreadB, NULL, NULL, NULL);

// 等待两个线程同时执行结束
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);

return 0;
}

事件

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
#include <stdio.h>
#include <windows.h>

// 事件: 可以选择自动还是手动
// 相关函数:
// 创建:CreateEvent
// 等待: WaitForXXXX
// 设置有信号: SetEvent
// 设置无信号: ResetEvent

// 创建一个事件对象
// 参数1: 安全属性
// 参数2: 是否手动,当使用手动时,不会产生副作用
// 参数3: 初始化的信号状态
// 参数4: 名字
HANDLE Event = CreateEvent(NULL, TRUE, TRUE, NULL);

// 全局变量,同时被多个线程访问
long Number = 0;

// 线程函数1
DWORD WINAPI WorkerThreadA(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
// 当处于自动模式下,使用等待函数的副作用改变信号状态,开始保护
// 【当处于手动模式下,等待函数不会产生副作用】
WaitForSingleObject(Event, INFINITE);
// ResetEvent(Event); 手动的调用 Reset 没有作用
Number++;
// 重新设置为有信号状态
SetEvent(Event);
}

return 0;
}

// 线程函数2
DWORD WINAPI WorkerThreadB(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(Event, INFINITE);
// ResetEvent(Event); 手动的调用 Reset 没有作用
Number++;
SetEvent(Event);
}

return 0;
}

int main()
{
HANDLE hThread1 = CreateThread(NULL, NULL, WorkerThreadA, NULL, NULL, NULL);
HANDLE hThread2 = CreateThread(NULL, NULL, WorkerThreadB, NULL, NULL, NULL);

// 等待两个线程同时执行结束
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);

return 0;
}

信号量

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
#include <stdio.h>
#include <windows.h>

// 信号量: 可以设置多把锁
// 相关函数:
// 创建:CreateSemaphore
// 开始保护(上一把锁): WaitForXXXX
// 解锁: ReleaseSemaphore

// 创建一个信号量对象
// 参数1: 安全属性
// 参数2: 初始化的锁的个数 当前的信号数量
// 参数3: 锁的最大个数 最多能够拥有多少信号
// 参数4: 名字
// 【当信号的数量为0的时候,表示所有的锁都锁上了】
HANDLE Semaphore = CreateSemaphore(NULL, 1, 1, NULL);

// 全局变量,同时被多个线程访问
long Number = 0;

// 线程函数1
DWORD WINAPI WorkerThreadA(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
// 上一把锁 拥有的信号数 -1
WaitForSingleObject(Semaphore, INFINITE);
Number++;

// 参数1: 需要对哪一个信号量操作
// 参数2: 解多少把锁 信号+
// 参数3: 解锁前的,锁的个数
LONG Count = 0;
ReleaseSemaphore(Semaphore, 1, &Count);
}

return 0;
}

// 线程函数2
DWORD WINAPI WorkerThreadB(LPVOID lpThreadParameter)
{
// 将全局变量 Number 自增十万次
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(Semaphore, INFINITE);
Number++;
LONG Count = 0;
ReleaseSemaphore(Semaphore, 1, &Count);
}

return 0;
}

int main()
{
HANDLE hThread1 = CreateThread(NULL, NULL, WorkerThreadA, NULL, NULL, NULL);
HANDLE hThread2 = CreateThread(NULL, NULL, WorkerThreadB, NULL, NULL, NULL);

// 等待两个线程同时执行结束
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);

return 0;
}