【Win32 多线程程序设计基础第二章笔记】
Win32 多线程程序设计:线程的第一次接触(零基础入门)
🌹 作者:云小逸
🤟 个人主页: 云小逸的主页
🤟 motto: 要敢于一个人默默的面对自己,强大自己才是核心。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在!学会自己和解,与过去和解,努力爱自己。希望春天来之前,我们一起面朝大海,春暖花开!
🥇 专栏:
文章目录
- Win32 多线程程序设计:线程的第一次接触(零基础入门)
-
- 📚 前言
- 一、为什么要学多线程?—— 先搞懂 “为什么需要它”
- 二、线程的基本概念 —— 先分清 “线程” 和 “进程”
- 三、产生第一个线程 ——CreateThread 函数详解
- 四、线程函数的 “强制格式”—— 必须这么写!
- 五、实战:产生第一个多线程程序
- 六、多线程的 “坑”—— 执行结果的无序性与竞争条件
- 七、线程的 “身份证” 与 “借书卡”—— 线程 ID、句柄与核心对象
- 八、如何知道线程 “干完活了”?—— 线程结束代码与 GetExitCodeThread
- 九、如何 “强制结束” 线程?——ExitThread 函数
- 十、多线程的 “安全网”—— 错误处理与 MTVERIFY 宏
- 十一、实战项目:后台打印程序 —— 多线程的实际应用
- 十二、第二章核心知识点总结
- 📣 结语
📚 前言
对于零基础学习 Win32 编程的同学来说,“多线程” 可能是第一个让人困惑的概念 —— 为什么不能用单线程写完所有功能?多线程到底能解决什么问题?本章会从 “实际场景” 出发,先帮你搞懂多线程的核心价值,再一步步拆解线程的基本概念、创建方法、常见问题及实战应用,全程搭配通俗解释和可运行代码,让你从 “完全不懂” 到 “能写出第一个多线程程序”。
一、为什么要学多线程?—— 先搞懂 “为什么需要它”
在学具体技术前,我们先解决一个核心问题:为什么需要多线程?
想象一个场景:你用软件编辑文档,点击 “打印” 后,软件开始生成打印数据。如果是单线程程序,这时候你不能再编辑文档 —— 软件会 “卡住”,直到打印数据生成完。但多线程能让 “生成打印数据” 和 “编辑文档” 同时进行:一个线程(worker 线程)默默处理打印,另一个线程(GUI 线程)响应用户的编辑操作。
简单说,多线程能让程序 “同时做多个事”,避免因一个耗时操作(如打印、文件读取)卡住整个界面,提升用户体验和程序效率。
二、线程的基本概念 —— 先分清 “线程” 和 “进程”
在 Win32 环境中,“进程” 和 “线程” 是两个极易混淆的概念,我们用 “容器” 和 “执行者” 的关系来理解:
-
进程(Process):是程序的 “容器”,包含程序运行所需的内存空间、资源(如窗口、文件句柄),比如你打开的 “记事本” 就是一个进程;
-
线程(Thread):是进程内的 “执行单元”,CPU 真正调度和执行的是线程,比如记事本的 “编辑文字” 和 “自动保存” 就是两个独立的线程。
1. 进程与线程的核心关系
-
一个进程至少有一个线程(称为 “主线程”,程序启动后默认执行的线程);
-
多个线程共享进程的内存和资源(如全局变量、文件句柄),但每个线程有自己的 “栈”(存储局部变量,互不干扰)。
2. 通俗示例
打开 “微信”(一个进程),里面的 “聊天窗口接收消息”“朋友圈加载图片”“文件传输” 就是三个独立的线程 —— 它们共享微信的内存(你的账号信息、聊天记录),但各自独立执行,不会因为 “文件传输” 而导致 “接收消息” 卡住。
三、产生第一个线程 ——CreateThread 函数详解
要创建线程,Win32 提供了CreateThread函数,这是多线程编程的 “入门钥匙”。我们从函数原型、参数含义、线程函数格式三个维度讲透,确保你能独立调用。
1. CreateThread 函数原型与参数
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性(一般传NULL)
DWORD dwStackSize, // 线程栈大小(0=默认1MB)
LPTHREAD_START_ROUTINE lpStartAddress, // 线程函数地址(核心)
LPVOID lpParameter, // 传给线程的参数(任意数据)
DWORD dwCreationFlags, // 创建标志(0=立即执行)
LPDWORD lpThreadId // 输出线程ID(不能传NULL,Win95不支持)
);
2. 参数通俗解释(零基础必看)
| 参数名 | 作用 | 零基础理解示例 |
|---|---|---|
| lpThreadAttributes | 线程的安全权限(如是否允许子进程继承),Windows 95 忽略,直接传 NULL。 | 不用管,填 NULL 就行。 |
| dwStackSize | 线程栈的大小(单位:字节),0 表示用默认值(1MB),一般传 0。 | 线程的 “临时工作空间”,默认够大,填 0。 |
| lpStartAddress | 线程要执行的函数地址(必须是特定格式的函数),核心参数。 | 线程启动后要 “干活的函数”。 |
| lpParameter | 传给线程函数的参数(可以是任意数据,用 LPVOID 强制转换)。 | 给 “干活函数” 的 “工具”(如打印内容)。 |
| dwCreationFlags | 线程创建后是否立即执行:0 = 立即执行;CREATE_SUSPENDED = 暂停执行。 | 一般填 0,让线程马上开始干活。 |
| lpThreadId | 输出参数,存储新线程的 ID(唯一标识系统中的线程),必须传有效指针。 | 线程的 “身份证号”,存到变量里。 |
3. 返回值说明
-
成功:返回一个 HANDLE(线程句柄),代表对 “线程核心对象” 的引用;
-
失败:返回 NULL,调用
GetLastError()可查看失败原因(如内存不足)。
四、线程函数的 “强制格式”—— 必须这么写!
CreateThread对 “线程要执行的函数” 有严格要求,格式错了会编译失败或程序崩溃,这是零基础最容易踩的坑,一定要牢记。
1. 强制格式
// 格式:返回值 DWORD,调用约定 WINAPI,参数 LPVOID
DWORD WINAPI 线程函数名(LPVOID 传入参数) {
// 线程要做的事情
return 0; // 线程结束代码(传给 GetExitCodeThread)
}
2. 为什么要这个格式?
因为CreateThread是 Win32 API,它要求线程函数必须按照这个 “约定” 传递参数和返回值 —— 就像快递员必须按地址送包裹,格式不对,CPU 无法正确 “接收” 线程的执行结果,程序会出错。
3. 示例:最简单的线程函数
// 线程函数:接收一个整数参数,打印10次该整数
DWORD WINAPI PrintNumThread(LPVOID param) {
int num = (int)param; // 将 LPVOID 转换为实际类型(这里是int)
for (int i = 0; i < 10; i++) {
printf("%d", num); // 打印参数
}
printf("n");
return num 10; // 线程结束代码(示例:返回参数的10倍)
}
五、实战:产生第一个多线程程序
我们写一个完整程序:启动 5 个线程,每个线程打印自己的参数(0~4)10 次,通过代码实操理解多线程的执行逻辑。
1. 完整代码(带详细注释)
#define WIN32_LEAN_AND_MEAN // 减少头文件冗余
#include <stdio.h>
#include <windows.h>
// 1. 声明线程函数(必须符合格式)
DWORD WINAPI PrintNumThread(LPVOID param);
int main() {
HANDLE hThread; // 线程句柄
DWORD threadId; // 线程ID
int i;
// 2. 循环创建5个线程
for (i = 0; i < 5; i++) {
// 调用 CreateThread 创建线程
hThread = CreateThread(
NULL, // 安全属性:默认
0, // 栈大小:默认
PrintNumThread, // 线程函数:PrintNumThread
(LPVOID)i, // 传给线程的参数:当前循环变量i
0, // 创建标志:立即执行
&threadId // 输出线程ID到threadId
);
// 检查是否创建成功
if (hThread != NULL) {
printf("成功启动线程,参数:%d,线程ID:%lun", i, threadId);
// 3. 释放线程句柄(避免资源泄漏)
CloseHandle(hThread);
} else {
printf("创建线程 %d 失败!n", i);
}
}
// 4. 等待子线程结束(简单方法:Sleep 2秒,实际项目用WaitForSingleObject)
Sleep(2000);
return 0;
}
// 5. 实现线程函数
DWORD WINAPI PrintNumThread(LPVOID param) {
int num = (int)param; // 转换参数类型
for (int i = 0; i < 10; i++) {
printf("%d", num);
}
return 0;
}
2. 编译与运行注意事项
-
编译时需指定 “多线程 C 运行时库”,否则
printf可能输出混乱:用 Visual Studio 编译:项目属性 → C/C++ → 代码生成 → 运行时库 → 选择 “多线程 (/MT)” 或 “多线程 DLL (/MD)”;
用命令行编译:
cl /MT 程序名.c。 -
运行结果:每次执行的打印顺序可能不同(如先打印 2 的线程,再打印 0 的线程),这是 CPU 调度的正常现象,不用慌!
六、多线程的 “坑”—— 执行结果的无序性与竞争条件
刚接触多线程时,很多同学会疑惑:“为什么每次运行结果都不一样?” 这其实是多线程的正常特性,也是需要解决的 “坑”。
1. 无序性的原因
Win32 是 “抢先式多任务”——CPU 会在多个线程间快速切换(称为 “上下文切换”),每个线程只执行一小段时间(“时间片”)。比如两个线程同时执行printf,可能线程 A 打印了一半,CPU 突然切换到线程 B,导致输出混乱(如预期 “000111” 变成 “010101”)。
2. 示例:竞争条件(Race Condition)
// 问题代码:两个线程同时打印,输出混乱
DWORD WINAPI ThreadA(LPVOID param) {
for (int i = 0; i < 5; i++) printf("A");
return 0;
}
DWORD WINAPI ThreadB(LPVOID param) {
for (int i = 0; i < 5; i++) printf("B");
return 0;
}
int main() {
CreateThread(NULL, 0, ThreadA, NULL, 0, NULL);
CreateThread(NULL, 0, ThreadB, NULL, 0, NULL);
Sleep(1000);
return 0;
}
-
可能的输出:
AABABABBB(而非预期的AAAAABBBBB),因为两个线程 “抢着” 写控制台。 -
解决方法:使用多线程 C 运行时库(/MT 或 /MD),或用 “同步机制”(如临界区)保护
printf(后续章节详解)。
七、线程的 “身份证” 与 “借书卡”—— 线程 ID、句柄与核心对象
在 Win32 中,线程有两个关键标识:线程 ID和线程句柄(HANDLE),它们的作用完全不同,必须分清,否则会导致代码出错。
1. 线程 ID(Thread ID)
-
是一个 DWORD 类型的整数,全局唯一(系统中所有线程的 ID 不重复);
-
用途:标识线程(如调试时区分不同线程),调用
PostThreadMessage(线程间发消息)需要线程 ID; -
局限性:无法通过 ID 直接操作线程(如等待线程结束),必须用 “句柄”。
2. 线程句柄(Thread Handle)
-
是 “线程核心对象(Kernel Object)” 的引用,类似 “图书馆的借书卡”—— 持有句柄,就有操作线程的权限(如等待线程结束、获取结束代码);
-
核心特性:核心对象有 “引用计数”—— 线程创建时,引用计数默认是 2(一个来自
CreateThread返回的句柄,一个来自线程本身); -
关键注意:必须调用
CloseHandle释放句柄,否则会导致 “资源泄漏”(即使线程结束,核心对象也不会销毁,占用内存)。
3. 引用计数的通俗理解
用 “图书馆借书” 类比:
-
你借走《Win32 多线程》(
CreateThread),获得一张借书卡(句柄),引用计数 = 1; -
图书馆为了跟踪书籍,自己留了一张记录(线程本身持有),引用计数 = 2;
-
你还书(
CloseHandle),引用计数 = 1; -
你看完书还回图书馆(线程结束),引用计数 = 0,图书馆销毁书籍记录(核心对象销毁)。
4. 实战:正确释放句柄
int main() {
HANDLE hThread = CreateThread(NULL, 0, PrintNumThread, (LPVOID)1, 0, &threadId);
if (hThread != NULL) {
// 必须调用 CloseHandle 释放句柄
CloseHandle(hThread);
hThread = NULL; // 避免野指针(防止后续误操作)
}
Sleep(1000);
return 0;
}
八、如何知道线程 “干完活了”?—— 线程结束代码与 GetExitCodeThread
有时候我们需要判断线程是否还在运行,或获取线程的执行结果(如线程计算出的数值),这就需要GetExitCodeThread函数。
1. GetExitCodeThread 函数原型
BOOL GetExitCodeThread(
HANDLE hThread, // 线程句柄(必须是有效的)
LPDWORD lpExitCode // 输出:线程的结束代码
);
2. 参数与返回值说明
-
hThread:线程的句柄(CreateThread返回的,且未被CloseHandle释放); -
lpExitCode:输出参数,存储线程的结束代码:-
线程仍在运行:
lpExitCode会被设为STILL_ACTIVE(值为 0x00000103); -
线程已结束:
lpExitCode会被设为线程函数的返回值(如线程函数return 10,则为 10);
-
-
返回值:成功返回 TRUE,失败返回 FALSE(如句柄无效)。
3. 实战:检查线程是否结束
启动两个线程,等待用户按任意键后,检查线程状态:
#include <conio.h> // 用于 getch()(等待用户按键)
DWORD WINAPI SlowThread(LPVOID param) {
int num = (int)param;
Sleep(num 1000); // 线程1睡1秒,线程2睡2秒
return num 10; // 结束代码:10(线程1)或20(线程2)
}
int main() {
HANDLE hThrd1, hThrd2;
DWORD exitCode1, exitCode2;
DWORD threadId;
// 启动两个线程
hThrd1 = CreateThread(NULL, 0, SlowThread, (LPVOID)1, 0, &threadId);
hThrd2 = CreateThread(NULL, 0, SlowThread, (LPVOID)2, 0, &threadId);
// 等待用户按键
printf("按任意键检查线程状态...n");
getch();
// 检查线程1状态
if (GetExitCodeThread(hThrd1, &exitCode1)) {
if (exitCode1 == STILL_ACTIVE) {
printf("线程1还在运行!n");
} else {
printf("线程1已结束,结束代码:%lun", exitCode1);
}
}
// 检查线程2状态
if (GetExitCodeThread(hThrd2, &exitCode2)) {
if (exitCode2 == STILL_ACTIVE) {
printf("线程2还在运行!n");
} else {
printf("线程2已结束,结束代码:%lun", exitCode2);
}
}
// 释放句柄
CloseHandle(hThrd1);
CloseHandle(hThrd2);
return 0;
}
4. 运行结果分析
-
1 秒内按按键:线程 1(睡 1 秒)和线程 2(睡 2 秒)都在运行,输出 “线程 1 还在运行!”“线程 2 还在运行!”;
-
2 秒后按按键:线程 1 和线程 2 都已结束,输出 “线程 1 已结束,结束代码:10”“线程 2 已结束,结束代码:20”。
九、如何 “强制结束” 线程?——ExitThread 函数
线程结束有两种方式,一种是 “自然结束”,一种是 “强制结束”,我们分别讲解。
1. 线程结束的两种方式
-
自然结束:线程函数执行到
return语句,正常退出; -
强制结束:调用
ExitThread函数,直接终止线程(调用后不会返回,后续代码不执行)。
2. ExitThread 函数原型
VOID ExitThread(DWORD dwExitCode);
-
参数
dwExitCode:线程的结束代码(会被GetExitCodeThread获取); -
核心特点:调用后立即终止线程,函数本身不返回,线程内
ExitThread之后的代码永远不会执行。
3. 示例:调用 ExitThread 结束线程
DWORD WINAPI TestExitThread(LPVOID param) {
printf("线程开始执行...n");
ExitThread(4); // 强制结束,结束代码4
printf("这里的代码不会执行!n"); // 永远不会打印(线程已终止)
return 0;
}
int main() {
HANDLE hThread = CreateThread(NULL, 0, TestExitThread, NULL, 0, &threadId);
DWORD exitCode;
if (hThread != NULL) {
// 等待线程结束(后续章节详解WaitForSingleObject)
WaitForSingleObject(hThread, INFINITE);
GetExitCodeThread(hThread, &exitCode);
printf("线程结束代码:%lun", exitCode); // 输出4
CloseHandle(hThread);
}
return 0;
}
4. 关键注意:主线程结束的影响
主线程(main 函数所在线程)结束会强制所有子线程结束,即使子线程还在运行。比如:
int main() {
// 子线程睡5秒(本应执行5秒)
CreateThread(NULL, 0, SlowThread, (LPVOID)5, 0, NULL);
// 主线程直接返回,强制子线程结束
return 0;
}
-
运行结果:子线程刚启动就被终止,不会执行完 5 秒的
Sleep; -
解决方法:主线程必须等待所有子线程结束后再退出(用
WaitForSingleObject或WaitForMultipleObjects,后续章节详解)。
十、多线程的 “安全网”—— 错误处理与 MTVERIFY 宏
Win32 函数(如CreateThread、CloseHandle)可能失败(比如内存不足、句柄无效),如果不检查错误,程序会莫名崩溃,调试时找不到原因。第二章推荐使用MTVERIFY 宏,它能自动检查函数返回值,失败时打印详细错误信息。
1. MTVERIFY 宏的原理
宏的核心逻辑:调用 Win32 函数 → 检查返回值 → 失败时获取错误原因并打印(包含函数名、行号、错误码)。
宏定义在MtVerify.h中(附录 A 有完整代码),基本格式如下:
#define MTVERIFY(expr)
do {
BOOL _success = (expr);
if (!_success) {
DWORD _err = GetLastError();
printf("在文件 %s 第 %d 行,调用 %s 失败!n原因:%lun",
__FILE__, __LINE__, #expr, _err);
}
} while(0)
2. 实战:用 MTVERIFY 检查错误
#include "MtVerify.h" // 包含MTVERIFY宏
int main() {
DWORD threadId, exitCode;
// 检查线程创建是否成功
HANDLE hThread = CreateThread(NULL, 0, PrintNumThread, (LPVOID)1, 0, &threadId);
MTVERIFY(hThread != NULL);
// 错误场景:释放句柄后再调用GetExitCodeThread
MTVERIFY(CloseHandle(hThread));
// 这里会失败(hThread已无效),MTVERIFY自动打印错误
MTVERIFY(GetExitCodeThread(hThread, &exitCode));
return 0;
}
3. 运行结果(错误提示)
在文件 test.c 第 12 行,调用 GetExitCodeThread(hThread, &exitCode) 失败!
原因:6(ERROR_INVALID_HANDLE,无效的句柄)
通过错误码 “6”,我们能快速定位问题 —— 句柄已被释放,无法再使用。
十一、实战项目:后台打印程序 —— 多线程的实际应用
第二章的核心实战是 “后台打印”,通过这个项目,我们学习GUI 线程与 Worker 线程的分工、线程间数据传递、安全等待线程结束,把前面的知识点串联起来。
1. 核心概念:GUI 线程与 Worker 线程
-
GUI 线程:负责处理用户界面(如窗口、按钮、对话框),必须有 “消息循环”(处理鼠标、键盘操作);
-
Worker 线程:负责 “后台工作”(如打印、计算、文件读取),不处理界面,避免卡住 GUI 线程;
-
关键规则:Worker 线程不能直接创建窗口、对话框(会导致界面混乱),需通过 GUI 线程处理界面操作。
2. 后台打印程序的核心逻辑
-
GUI 线程:显示 “编辑框” 和 “打印按钮”,用户输入文字后点击 “打印”;
-
GUI 线程:弹出 “打印对话框”,获取打印机 DC(设备上下文,用于打印输出);
-
GUI 线程:在堆内存中创建
ThreadPrintInfo结构体,存储打印所需数据(文字、打印机 DC、窗口句柄); -
GUI 线程:调用
CreateThread启动 Worker 线程(处理打印); -
Worker 线程:生成打印数据(如设置文字格式),输出到打印机;
-
GUI 线程:等待所有 Worker 线程结束后,再允许程序退出。
3. 关键代码解析(核心部分)
(1)定义传递数据的结构体(避免全局变量)
// 存储打印所需的参数(堆内存分配,避免栈变量失效)
typedef struct {
HWND hWndParent; // GUI窗口句柄(用于通知界面)
HDC hDc; // 打印机DC(打印输出用)
BOOL bPrint; // 是否打印(TRUE=打印,FALSE=预览)
char szText[256]; // 要打印的文字(用户输入)
} ThreadPrintInfo;
(2)GUI 线程:启动 Worker 线程(点击 “打印” 按钮时调用)
void OnPrint(HWND hWnd, char szText) {
PRINTDLG dlgPrint = {0};
dlgPrint.lStructSize = sizeof(PRINTDLG);
dlgPrint.hwndOwner = hWnd;
dlgPrint.Flags = PD_RETURNDC; // 让PrintDlg返回打印机DC
// 1. 弹出打印对话框,获取打印机DC
if (!PrintDlg(&dlgPrint)) {
return;
}
// 2. 在堆内存中分配ThreadPrintInfo(避免栈变量失效)
ThreadPrintInfo pInfo = HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(ThreadPrintInfo)
);
if (pInfo == NULL) {
MessageBox(hWnd, "内存分配失败!", "错误", MB_OK);
return;
}
// 3. 初始化参数
pInfo->hWndParent = hWnd;
pInfo->hDc = dlgPrint.hDC;
pInfo->bPrint = TRUE;
strcpy(pInfo->szText, szText); // 复制用户输入的文字
// 4. 启动Worker线程(处理打印)
static HANDLE gPrintThreads[10]; // 存储线程句柄(最多10个打印线程)
static int gThreadCount = 0; // 线程计数
HANDLE hThread = CreateThread(
NULL,
0,
BackgroundPrintThread, // Worker线程函数
(LPVOID)pInfo, // 传递堆内存的参数
0,
&threadId
);
if (hThread != NULL) {
gPrintThreads[gThreadCount++] = hThread; // 存储句柄,后续等待
CloseHandle(hThread); // 释放句柄(线程仍在运行)
} else {
HeapFree(GetProcessHeap(), 0, pInfo); // 创建失败,释放内存
}
}
(3)Worker 线程:处理打印(核心工作逻辑)
// Worker线程函数:处理打印
DWORD WINAPI BackgroundPrintThread(LPVOID param) {
ThreadPrintInfo pInfo = (ThreadPrintInfo)param;
HDC hDc = pInfo->hDc;
char szText = pInfo->szText;
// 1. 初始化打印机(StartDoc:开始打印文档,StartPage:开始打印页)
DOCINFO docInfo = {0};
docInfo.cbSize = sizeof(DOCINFO);
docInfo.lpszDocName = "后台打印示例"; // 打印文档名
StartDoc(hDc, &docInfo);
StartPage(hDc);
// 2. 打印文字(简化为绘制文字,实际项目可设置字体、颜色)
RECT rect = {50, 50, 500, 100}; // 打印位置(左、上、右、下)
DrawText(hDc, szText, -1, &rect, DT_LEFT); // 绘制文字到打印机DC
// 3. 结束打印(EndPage:结束当前页,EndDoc:结束打印文档)
EndPage(hDc);
EndDoc(hDc);
// 4. 释放资源(避免内存泄漏)
DeleteDC(hDc); // 释放打印机DC
HeapFree(GetProcessHeap(), 0, pInfo); // 释放堆内存参数
return 0;
}
(4)GUI 线程:等待所有 Worker 线程结束(程序退出时调用)
void OnExit() {
static HANDLE gPrintThreads[10];
static int gThreadCount = 0;
// 等待所有打印线程结束
for (int i = 0; i < gThreadCount; i++) {
DWORD exitCode;
// 循环检查线程是否结束(避免忙等占用CPU,每10毫秒查一次)
do {
GetExitCodeThread(gPrintThreads[i], &exitCode);
Sleep(10);
} while (exitCode == STILL_ACTIVE);
CloseHandle(gPrintThreads[i]); // 释放句柄
}
}
4. 关键注意事项
-
数据传递用堆内存:避免用全局变量(多个线程可能同时修改,导致错误)和栈变量(函数返回后栈变量失效,线程访问会出错);
-
不共享 GDI 对象:打印机 DC、画笔、画刷等 GDI 对象不能多个线程共享,否则会导致绘图错误;
-
主线程处理界面:Worker 线程如需显示信息(如 “打印完成” 提示),需通过
SendMessage通知 GUI 线程,由 GUI 线程弹出对话框。
十二、第二章核心知识点总结
-
线程创建:用
CreateThread函数,线程函数必须是DWORD WINAPI 函数名(LPVOID)格式; -
资源释放:线程句柄必须用
CloseHandle释放,避免资源泄漏; -
线程状态:用
GetExitCodeThread检查线程是否运行(STILL_ACTIVE表示正在运行); -
线程结束:
ExitThread强制结束线程,主线程结束会强制所有子线程结束; -
错误处理:用
MTVERIFY宏检查 Win32 函数错误,快速定位问题; -
实战原则:GUI 线程处理界面,Worker 线程处理后台工作,数据用堆内存传递,避免共享资源。
📣 结语
感谢你耐心看完这章内容!恭喜你从 “完全不懂多线程” 到 “能理解线程概念、写出第一个多线程程序、解决简单的多线程问题”—— 这是一个不小的进步!
多线程是 Win32 编程的核心难点,但也是提升程序性能的关键技术。本章我们打下了基础,后续章节会深入 “同步机制”(解决竞争条件)、“线程优先级”“Overlapped I/O” 等高级内容。
如果你觉得这章内容对你有帮助,记得给我点赞、收藏和关注哦(。・ω・。)!让我们一起加油,从零基础新手逐渐成长为 Win32 编程高手,每一步进步都值得为自己点赞!
更多推荐

所有评论(0)