【Win32 多线程程序设计基础第三章笔记】
🌹 作者: 云小逸
🤟 个人主页: 云小逸的主页
🤟 motto: 要敢于一个人默默的面对自己,强大自己才是核心。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在!学会自己和解,与过去和解,努力爱自己。希望春天来之前,我们一起面朝大海,春暖花开!
🥇 专栏:
- WTL学习
- 动态规划
- C 语言
- C++
- Java 语言
- Linux 编程
- 算法
- 待续…
文章目录
- 📚 前言
- 一. 等待线程结束的“痛点”——忙等(Busy Waiting)
- 1. 忙等的本质与场景类比
- 2. 忙等的代码实现(文档第2章简化版)
- 3. 忙等的致命问题:CPU被占满
- 二. 核心原理:用“线程信号灯”实现高效等待
- 1. 核心概念:线程核心对象与激发状态
- 2. 信号灯的两种状态(对应线程生命周期)
- 3. 信号灯的系统管理逻辑
- 三. 具体操作:用WaitForSingleObject“等灯变绿”
- 1. 第一步:创建线程(生成“信号灯”)
- 2. 第二步:调用函数等待“信号灯变绿”
- 3. 函数返回值解读(文档明确的3种场景)
- 4. 第三步:“信号灯”用完要还(关闭句柄)
- 四. 关键提醒:3个易踩的坑
- 1. 不能等“已还的信号灯”
- 2. “灯绿了就不会再红”
- 3. 超时时间别乱设
- 五. WM_TIMER:GUI程序的“定时触发”方案
- 1. WM_TIMER的本质定位(文档背景)
- 2. WM_TIMER的完整工作流程
- (1)步骤1:创建定时器(SetTimer函数)
- (2)步骤2:定时器触发(系统发送WM_TIMER消息)
- (3)步骤3:处理WM_TIMER消息(依赖主消息循环)
- ① 主消息循环基础结构(文档核心片段)
- ② WM_TIMER的处理逻辑
- (4)步骤4:销毁定时器(KillTimer函数)
- 3. WM_TIMER的核心特性(文档隐含细节)
- 六. 等待多个对象:线程池的高效复用
- 1. 场景背景
- 2. TaskQueS程序的低效原因(文档列表3-2)
- (1)TaskQueS核心逻辑
- (2)低效根源:错误假设“线程按创建顺序结束”
- 3. 解决方案:WaitForMultipleObjects函数(文档核心)
- (1)函数原型与关键参数
- (2)关键返回值规则
- 4. TaskQueM程序的优化实现(文档列表3-3)
- (1)任务执行阶段:精准复用结束的线程
- (2)程序收尾阶段:一次等待所有线程
- 七. GUI程序中的等待:兼顾“等对象”与“UI响应”
- 1. GUI程序的核心矛盾:等待与响应的冲突
- (1)传统主消息循环的作用
- (2)传统等待函数的缺陷
- 2. 解决方案:MsgWaitForMultipleObjects函数(文档核心)
- (1)函数原型与关键参数
- (2)关键返回值规则
- (3)改写后的主消息循环骨架(文档列表3-4)
- 3. 实际应用:PRNTWAIT范例程序(文档BACKPRNT改进版)
- WaitForMultipleObjects 与 MsgWaitForMultipleObjects 函数对比表
- 八. WaitForSingleObject:超时参数与核心对象激发状态
- 1. WaitForSingleObject的timeout参数用途
- (1)timeout=0:非阻塞检查handle状态
- (2)避免“被粘住”与调试友好
- 2. 核心对象的激发状态(文档表3-1核心内容)
- 3. 关键FAQ解答(对应文档FAQ10-12)
- Win32多线程第三章核心等待函数对比表
- 📣 结语
📚 前言
本文档基于《Win32多线程程序设计.pdf》第三章内容整理,全程围绕Win32多线程中“等待机制”展开,针对零基础读者设计——不堆砌复杂术语,而是用“朋友打印文件”“线程信号灯”等通俗比喻,结合文档中原版代码案例,拆解“为什么要高效等待”“如何等待单个线程”“如何定时触发任务”“如何等待多个对象”“GUI程序如何兼顾等待与UI响应”等核心问题,所有知识点均来自指定文档,未添加外部内容,确保内容的准确性与针对性。
一. 等待线程结束的“痛点”——忙等(Busy Waiting)
1. 忙等的本质与场景类比
在《Win32多线程程序设计.pdf》第2章中,等待线程结束用了一种“笨办法”——忙等。它就像你让朋友帮忙打印文件,你不坐下等,而是每隔1秒就去问“好了吗?好了吗?”——全程啥也不干,只反复追问,这就是忙等的核心逻辑。
2. 忙等的代码实现(文档第2章简化版)
// 第2章的笨办法:忙等
while (1) {DWORD exitCode;// 反复问:线程结束了吗?GetExitCodeThread(hThrd, &exitCode);// 没结束就继续问,结束了才停if (exitCode != STILL_ACTIVE) break;
}
3. 忙等的致命问题:CPU被占满
文章中通过实测证实了忙等的低效:计算圆周率时,直接调用函数仅需8秒,而用忙等等待线程却要16秒。
原因很简单:你(主线程)和朋友(Worker线程)在抢CPU——你反复调用GetExitCodeThread
追问的时间,挤占了Worker线程执行任务的时间,导致整体效率翻倍下降。本章的核心目标,就是用“聪明办法”替代忙等。
二. 核心原理:用“线程信号灯”实现高效等待
1. 核心概念:线程核心对象与激发状态
要理解高效等待,必须先懂《Win32多线程程序设计.pdf》中反复强调的**“线程核心对象”和“激发状态”** ——我们可以将其比作“线程专属信号灯”,这个信号灯由Windows系统自动管理,无需手动控制。
2. 信号灯的两种状态(对应线程生命周期)
- 线程正在运行时:信号灯为红色(未激发状态)。此时调用等待函数,主线程会“休眠”(像你坐下喝茶),完全不占用CPU;
- 线程执行结束时:信号灯自动变为绿色(激发状态)。操作系统会立刻唤醒主线程(你收到朋友的“完成通知单”),等待函数返回,主线程继续处理后续逻辑。
3. 信号灯的系统管理逻辑
- 生成:调用
CreateThread
创建线程时,系统会自动生成这个“信号灯”——也就是CreateThread
返回的hThrd
句柄(可理解为“信号灯编号”); - 状态切换:线程结束时,系统会自动将信号灯从红色转为绿色,全程无需开发者写任何控制代码。
三. 具体操作:用WaitForSingleObject“等灯变绿”
《Win32多线程程序设计.pdf》中的WaitForSingleObject
函数,是专门“等信号灯变绿”的工具。结合“朋友打印文件”场景,拆解操作步骤(代码均来自文档,未修改)。
1. 第一步:创建线程(生成“信号灯”)
先让朋友(Worker线程)开始打印,同时拿到系统分配的“信号灯”(hThrd
句柄),代码如下:
// 1. 让朋友开始打印(创建Worker线程)
HANDLE hThrd; // 这就是“线程信号灯”的编号
DWORD threadId;
hThrd = CreateThread(NULL, // 安全属性(默认,不用管)0, // 朋友的工作台大小(默认)PrintFile, // 朋友要干的活(打印函数)NULL, // 给朋友的参数(比如打印内容)0, // 让朋友立刻开始干活&threadId // 记录朋友的ID(备用)
);
文档强调:hThrd
是关键——它是“信号灯”的唯一标识,后续“等灯变绿”必须依赖它。
2. 第二步:调用函数等待“信号灯变绿”
你坐下喝茶(主线程休眠),等待朋友把“信号灯”变绿,代码如下:
// 2. 等信号灯变绿(朋友打印结束)
DWORD waitResult = WaitForSingleObject(hThrd, // 要等的“信号灯”编号INFINITE // 等多久:无限等,直到灯变绿(没耐心可设具体时间,如3000=3秒)
);
3. 函数返回值解读(文档明确的3种场景)
函数返回值 | 对应场景(打印例子) | 后续操作 |
---|---|---|
WAIT_OBJECT_0 | 信号灯变绿(朋友打印完了) | 主线程唤醒,处理后续(如检查打印结果) |
WAIT_TIMEOUT | 等了指定时间(如3秒),灯仍红(朋友没弄完) | 可选择再等一次,或强制结束线程(文档第5章内容) |
WAIT_FAILED | 拿错“信号灯”编号(hThrd无效) | 调用GetLastError() 查因(如编号被误删) |
4. 第三步:“信号灯”用完要还(关闭句柄)
朋友离开后,“信号灯”需还给系统,否则会导致“资源泄漏”(系统持续占用编号),代码如下:
// 3. 把“信号灯”还给系统(避免浪费)
CloseHandle(hThrd);
文档中ERROR.C
程序演示了错误用法:若先调用CloseHandle(hThrd)
(先还信号灯),再调用WaitForSingleObject
,函数会返回WAIT_FAILED
(用无效编号等待,必然失败)。
四. 关键提醒:3个易踩的坑
《Win32多线程程序设计.pdf》强调了以下3个错误场景,结合比喻更易记忆:
1. 不能等“已还的信号灯”
就像把身份证挂失后,再用旧身份证办事——肯定失败。必须遵循“先等待(WaitForSingleObject),后还信号灯(CloseHandle)”的顺序。
2. “灯绿了就不会再红”
线程结束后,“信号灯”会永久保持绿色——即使再次调用WaitForSingleObject
,也会立刻返回WAIT_OBJECT_0
。若想重新等待线程,必须重新创建线程(生成新的信号灯)。
3. 超时时间别乱设
- 设
INFINITE
(无限等):需确保线程一定会结束(如朋友肯定会完成打印); - 线程可能“卡住”(如朋友忘记打印):设具体时间(如30000毫秒=30秒),超时后执行异常处理(如提示用户“任务超时”)。
五. WM_TIMER:GUI程序的“定时触发”方案
1. WM_TIMER的本质定位(文档背景)
WM_TIMER是Windows为GUI程序设计的系统级定时器消息,属于“消息驱动模型”的一部分(依赖GUI程序的消息循环),设计初衷是解决Win16环境下的低效等待问题:
- Win16没有
WaitForSingleObject
这类工具,忙等会浪费CPU; - WM_TIMER通过“消息队列异步触发”,让程序在等待期间仍能处理用户操作(如点击、窗口拖动),避免界面“假死”。
文档特别提到:WM_TIMER能“漂亮地解决某些问题”(如定时更新界面),但实现比忙等复杂——需理解Windows消息机制。
2. WM_TIMER的完整工作流程
(1)步骤1:创建定时器(SetTimer函数)
需先通过SetTimer
向系统“注册定时器”,明确定时周期和处理逻辑(文档未列原型,结合Win32规范说明):
- 参数约定:需指定“接收消息的窗口句柄”“定时器ID(唯一标识)”“定时周期(毫秒)”“回调函数(可选)”;
- 系统注册:调用后,Windows记录定时器信息并开始计时;
- 文档关联:Win16程序需“回到主消息循环”才能响应事件,
SetTimer
的本质是让系统在定时到期时,将WM_TIMER送入窗口的消息队列。
(2)步骤2:定时器触发(系统发送WM_TIMER消息)
定时周期到期时,系统发送WM_TIMER消息,核心逻辑如下:
- 非实时触发:WM_TIMER是“低优先级消息”——仅当消息队列空闲、无高优先级消息(如键盘输入)时,才会被送入队列,不适用于“精确计时”(如秒表);
- 重复触发:未销毁定时器时,系统按周期重复发送WM_TIMER,直到调用
KillTimer
。
(3)步骤3:处理WM_TIMER消息(依赖主消息循环)
GUI程序的核心是“主消息循环”,WM_TIMER需通过该循环被捕获和处理。
① 主消息循环基础结构(文档核心片段)
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); // 转换消息(如键盘消息转字符消息)DispatchMessage(&msg); // 将消息分发到窗口的消息处理函数
}
GetMessage
:从消息队列取消息,无消息时“休眠”(不占CPU),直到有消息(包括WM_TIMER);DispatchMessage
:将消息分发到窗口的“消息处理函数”(如WndProc
),执行具体逻辑。
② WM_TIMER的处理逻辑
WM_TIMER被取出后,传入消息处理函数,程序需在函数中响应:
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {switch (msg) {case WM_TIMER: // 捕获WM_TIMER消息// 执行定时任务(如更新界面、检查线程状态)UpdateStatus(); // 文档中“漂亮解决问题”的具体逻辑break;case WM_DESTROY:PostQuitMessage(0); // 程序退出时发送WM_QUITbreak;default:return DefWindowProc(hWnd, msg, wParam, lParam); // 默认消息处理}return 0;
}
关键优势:处理WM_TIMER时,程序仍能接收其他消息(如用户点击按钮)——GetMessage
会继续读取队列,不会像忙等那样“霸占CPU”。
(4)步骤4:销毁定时器(KillTimer函数)
定时器属于系统资源,不销毁会导致资源泄漏(与线程句柄需CloseHandle
同理):
- 调用时机:无需定时任务时(如窗口关闭),传入“窗口句柄”和“定时器ID”;
- 系统行为:停止发送WM_TIMER,释放资源,避免程序退出后残留无效定时器。
3. WM_TIMER的核心特性(文档隐含细节)
- 非阻塞性:不影响界面响应——等待期间
GetMessage
休眠,CPU可处理其他消息(如窗口重绘); - 非精确性:计时有误差——高优先级消息会延后WM_TIMER处理,适合“周期性检查”(如更新进度条),不适合“微秒级计时”;
- 依赖GUI环境:仅适用于有消息循环的程序——控制台程序(如文档中的
NUMBERS.C
)无消息循环,无法使用WM_TIMER。
六. 等待多个对象:线程池的高效复用
文档以“用最多3个线程(线程池)完成6项任务”为场景,核心目标是“始终保持3个线程运行(除非任务不足)”,避免CPU空闲。
1. 场景背景
理想状态:3个线程同时干活,一个线程结束就立即用新任务复用它,让CPU始终满负荷。但最初的TaskQueS
程序因等待逻辑缺陷,未实现该目标。
2. TaskQueS程序的低效原因(文档列表3-2)
(1)TaskQueS核心逻辑
- 定义线程池大小
THREAD_POOL_SIZE=3
、任务总数NUM_TASKS=6
,用hThrds
数组存线程句柄,slot
表示复用的线程槽位(0、1、2循环); - 前3个任务(i=13):直接创建线程,填充`hThrds[0]`
hThrds[2]
; - 第4~6个任务(i>3):调用
WaitForSingleObject(hThrds[slot], INFINITE)
,等待当前slot
对应的线程结束,再创建新线程复用槽位。
(2)低效根源:错误假设“线程按创建顺序结束”
程序默认线程会按“slot 0→1→2”的顺序结束,但实际线程执行时间随机(Sleep
时长由rand()
生成),导致“盲目等待”:
文档输出案例显示:Slot1和Slot2先结束(打印“Slot 1 idle”“Slot 2 idle”),但主线程仍在等Slot0(因slot
循环到0)——此时Slot1和Slot2的槽位空闲却未被复用,出现“无线程运行的空窗期”,浪费CPU。
3. 解决方案:WaitForMultipleObjects函数(文档核心)
该函数能同时等待多个核心对象(如线程句柄),精准定位“最先变绿的信号灯”,解决“不知道哪个线程先结束”的问题。
(1)函数原型与关键参数
DWORD WaitForMultipleObjects( DWORD nCount, // handle数组元素个数(最大64)CONST HANDLE *lpHandles,// 待等待的对象句柄数组(支持不同类型)BOOL bWaitAll, // 等待策略:TRUE=等所有;FALSE=等任意一个DWORD dwMilliseconds // 超时时间(0=立即返回,INFINITE=无限等)
);
nCount
:文档中设为THREAD_POOL_SIZE=3
(线程池大小);bWaitAll
:核心参数,文档中两种用法:- 任务执行中(i>3):设为
FALSE
,仅等“任意一个线程结束”,避免盲目等待; - 程序末尾:设为
TRUE
,一次等待所有剩余线程结束,简化代码;
- 任务执行中(i>3):设为
dwMilliseconds
:文档中均设为INFINITE
,确保等线程结束(无超时)。
(2)关键返回值规则
- 若
bWaitAll=FALSE
:返回值=WAIT_OBJECT_0 + 激发对象的索引
——用“返回值 - WAIT_OBJECT_0”可直接获取最先结束线程的槽位; - 若
bWaitAll=TRUE
:所有对象激发时返回WAIT_OBJECT_0
; - 超时返回
WAIT_TIMEOUT
,失败返回WAIT_FAILED
(需调用GetLastError()
)。
4. TaskQueM程序的优化实现(文档列表3-3)
TaskQueM
用WaitForMultipleObjects
替代WaitForSingleObject
,核心优化点如下:
(1)任务执行阶段:精准复用结束的线程
当i>3(需复用线程)时,主线程不再等固定槽位,而是等“任意一个线程结束”:
rc = WaitForMultipleObjects(THREAD_POOL_SIZE, hThrds, FALSE, INFINITE);
slot = rc - WAIT_OBJECT_0; // 直接获取最先结束线程的槽位
文档输出可见:Slot1先结束后,主线程立即复用Slot1创建第4个任务,始终保持3个线程运行,无空窗期。
(2)程序收尾阶段:一次等待所有线程
程序末尾无需循环调用WaitForSingleObject
,一次调用即可等待所有剩余线程:
rc = WaitForMultipleObjects(THREAD_POOL_SIZE, hThrds, TRUE, INFINITE);
七. GUI程序中的等待:兼顾“等对象”与“UI响应”
GUI程序的核心矛盾:用WaitForSingleObject
等核心对象会阻塞主线程——主线程无法处理消息(如窗口重绘、用户点击),导致UI“假死”。文档用MsgWaitForMultipleObjects
函数解决该问题。
1. GUI程序的核心矛盾:等待与响应的冲突
(1)传统主消息循环的作用
GUI程序的主消息循环靠GetMessage
实现“非忙等”等待,类似“消息专用的WaitForSingleObject”:
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);
}
GetMessage
:无消息时阻塞(释放CPU),有消息时返回处理;- 核心原则:必须频繁回到消息循环,否则窗口无法重绘、菜单无响应。
(2)传统等待函数的缺陷
若用WaitForSingleObject
或WaitForMultipleObjects
等待核心对象,主线程会陷入“等对象激发”的循环,无法调用GetMessage
处理消息——直接导致UI卡死。
2. 解决方案:MsgWaitForMultipleObjects函数(文档核心)
该函数支持“同时等待核心对象激发或消息到达”,相当于“既能等信号灯变绿,又能听闹钟(消息)”,兼顾等待与UI响应。
(1)函数原型与关键参数
DWORD MsgWaitForMultipleObjects( DWORD nCount, // 待等待的核心对象个数LPHANDLE pHandles, // 核心对象句柄数组(如线程句柄)BOOL fWaitAll, // 等待策略:TRUE=等所有;FALSE=等任意一个DWORD dwMilliseconds,// 超时时间(INFINITE=无限等)DWORD dwWakeMask // 需监听的消息类型(核心新增参数)
);
dwWakeMask
:指定“哪些消息会唤醒函数”,文档中常用取值:QS_ALLINPUT
:监听所有输入消息(键盘、鼠标、定时器等);QS_PAINT
:仅监听窗口重绘消息;QS_TIMER
:仅监听定时器消息。
(2)关键返回值规则
返回值类型 | 含义 | 处理逻辑 |
---|---|---|
WAIT_OBJECT_0 ~ WAIT_OBJECT_0 + nCount - 1 | 对应索引的核心对象激发 | 处理该对象(如线程结束后的清理) |
WAIT_OBJECT_0 + nCount | 有符合dwWakeMask 的消息到达 | 调用GetMessage() 处理消息(避免UI卡死) |
WAIT_TIMEOUT | 超时(无对象激发且无消息) | 自定义超时逻辑(如更新进度条) |
(3)改写后的主消息循环骨架(文档列表3-4)
while (!quit) { MSG msg; int rc = MsgWaitForMultipleObjects( nWaitCount, // 等待的核心对象个数hWaitArray, // 核心对象句柄数组FALSE, // 等任意一个对象激发INFINITE, // 无限等待QS_ALLINPUT); // 监听所有输入消息if (rc == WAIT_OBJECT_0 + nWaitCount) { // 分支1:有消息到达,处理消息(避免UI卡死)while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { quit = TRUE; break; } TranslateMessage(&msg); DispatchMessage(&msg); } } else if (rc >= WAIT_OBJECT_0 && rc < WAIT_OBJECT_0 + nWaitCount) { // 分支2:某核心对象激发,处理对象(如线程结束)int nIndex = rc - WAIT_OBJECT_0; // 定位激发的对象索引// 自定义处理:如关闭线程句柄、更新任务状态} else if (rc == WAIT_TIMEOUT) { // 分支3:超时,处理超时逻辑}
}
3. 实际应用:PRNTWAIT范例程序(文档BACKPRNT改进版)
PRNTWAIT
支持“用户点击【Exit】后,等所有打印线程结束再退出”,其主消息循环是MsgWaitForMultipleObjects
的典型实践:
while (!quit || gNumPrinting > 0) { // 退出条件:quit为真且无打印线程DWORD dwWake = MsgWaitForMultipleObjects( gNumPrinting, // 待等待的打印线程个数gPrintJobs, // 打印线程句柄数组FALSE, // 等任意一个线程结束INFINITE, // 无限等待QS_ALLEVENTS); // 监听所有事件消息if (dwWake >= WAIT_OBJECT_0 && dwWake < WAIT_OBJECT_0 + gNumPrinting) { // 分支1:某打印线程结束,整理句柄数组(避免缝隙)int index = dwWake - WAIT_OBJECT_0; // 关键:将数组末尾句柄移到空槽,压缩数组(无NULL缝隙)gPrintJobs[index] = gPrintJobs[gNumPrinting-1]; gPrintJobs[gNumPrinting-1] = 0; gNumPrinting--; // 打印线程计数减1// 通知UI更新线程计数(如窗口显示“剩余2个打印任务”)SendMessage(hDlgMain, WM_THREADCOUNT, gNumPrinting, 0L); } else if (dwWake == WAIT_OBJECT_0 + gNumPrinting) { // 分支2:有消息到达,处理消息(如响应【Exit】)while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) quit = TRUE; // 标记退出,但不立即结束TranslateMessage(&msg); DispatchMessage(&msg); } }
}
WaitForMultipleObjects 与 MsgWaitForMultipleObjects 函数对比表
对比维度 | WaitForMultipleObjects | MsgWaitForMultipleObjects |
---|---|---|
核心用途 | 等待多个核心对象(如线程、事件)激发,不处理消息 | 等待多个核心对象激发 同时监听GUI消息,避免UI假死 |
等待对象数量 | 1~MAXIMUM_WAIT_OBJECTS(64个) | 1~MAXIMUM_WAIT_OBJECTS(64个) |
是否支持消息监听 | 不支持(仅等待核心对象,阻塞时无法处理消息) | 支持(通过dwWakeMask 参数指定监听的消息类型) |
关键参数差异 | 无消息相关参数,核心是bWaitAll (等所有/等任意) | 多dwWakeMask 参数(如QS_ALLINPUT监听所有输入消息、QS_PAINT监听重绘消息) |
典型应用场景 | 非GUI场景的多对象等待(如3线程池处理6任务、多资源就绪判断) | GUI程序的多对象等待(如后台打印时响应【Exit】点击、定时更新界面) |
句柄数组要求 | 需确保数组无NULL缝隙,对象激发后需手动整理数组 | 同左(文档PRNTWAIT程序强调:对象激发后需压缩数组,避免无效句柄报错) |
核心返回值标识 | 1. 等任意对象:WAIT_OBJECT_0 + 激发对象索引 2. 等所有对象:WAIT_OBJECT_0 3. 超时:WAIT_TIMEOUT | 1. 对象激发:WAIT_OBJECT_0 + 激发对象索引 2. 消息到达:WAIT_OBJECT_0 + 等待对象总数 3. 超时:WAIT_TIMEOUT |
文档特殊说明 | 解决TaskQueS程序“盲目等待线程顺序”问题,实现线程池高效复用 | 解决GUI程序“等待时UI卡死”问题(如PRNTWAIT程序监听消息并等待打印线程) |
八. WaitForSingleObject:超时参数与核心对象激发状态
1. WaitForSingleObject的timeout参数用途
timeout
定义函数等待核心对象的最长时间(毫秒),除基础“等待指定时间”外,还有两个关键用途:
(1)timeout=0:非阻塞检查handle状态
这是最特殊的用法,能“不等待、立即返回”,快速判断对象是否已激发:
- 若对象已激发(如线程已结束):返回
WAIT_OBJECT_0
(对象就绪); - 若对象未激发:立即返回
WAIT_TIMEOUT
(对象未就绪),无等待耗时; - 典型场景:循环中快速轮询多个对象状态,不阻塞其他逻辑。
(2)避免“被粘住”与调试友好
- 调试时:若等待的线程意外进入无穷循环,设合理
timeout
(如3000毫秒),函数超时后返回WAIT_TIMEOUT
,可判断“等待目标异常”,避免程序卡死; - 示例:等待线程结束时,每500毫秒超时一次,期间更新“加载中”动画——既让用户感知进度,又能及时响应异常。
2. 核心对象的激发状态(文档表3-1核心内容)
Win32中可被WaitForSingleObject
等待的核心对象,均有“激发”和“未激发”两种状态,不同对象的激发条件不同:
核心对象类型 | 激发状态触发条件 | 未激发状态场景 |
---|---|---|
线程(Thread) | 线程执行结束(正常返回或被终止) | 线程正在运行中 |
进程(Process) | 进程执行结束(主进程退出或被终止) | 进程正在运行中 |
Console 输入 | Console窗口输入缓冲区有数据(如用户按键) | 输入缓冲区为空 |
事件(Event) | 调用SetEvent() /PulseEvent() ,或Overlapped I/O完成时系统自动激发 | 调用ResetEvent() 后,或初始未激发状态 |
互斥器(Mutex) | 没有任何线程拥有该互斥器(未被锁定) | 互斥器已被某线程锁定 |
信号量(Semaphore) | 信号量计数器值>0(仍有可用“资源名额”) | 计数器值=0(资源已耗尽) |
3. 关键FAQ解答(对应文档FAQ10-12)
- FAQ10:如何得知核心对象是否处于激发状态?
用WaitForSingleObject(handle, 0)
——返回WAIT_OBJECT_0
=已激发,WAIT_TIMEOUT
=未激发; - FAQ11:什么是被激发的对象?
核心对象满足“等待目标”的状态,如线程结束、互斥器未被锁定,此时等待该对象的函数会结束等待; - FAQ12:“激发”对不同核心对象有什么不同意义?
核心差异在“激发触发条件”——线程是“结束即激发”,信号量是“计数器>0即激发”,具体见上文表格。
Win32多线程第三章核心等待函数对比表
函数名称 | 核心用途 | 等待对象数量 | 是否支持消息监听 | 典型应用场景 | 关键参数特点 | 核心返回值标识 |
---|---|---|---|---|---|---|
WaitForSingleObject | 等待单个核心对象激发(如线程结束、事件触发) | 1个(线程/事件/互斥器等) | 不支持 | 单一线程等待、资源就绪判断(如等待Worker线程结束) | 需传入“对象句柄”“超时时间(INFINITE/具体毫秒)” | WAIT_OBJECT_0(对象激发)、WAIT_TIMEOUT(超时)、WAIT_FAILED(失败) |
WaitForMultipleObjects | 等待多个核心对象激发 | 1~MAXIMUM_WAIT_OBJECTS(64个) | 不支持 | 线程池复用(如3个线程池处理6个任务)、多资源等待 | 需传入“对象数组”“等待策略(bWaitAll:TRUE等所有/FALSE等任意)” | 等任意对象:WAIT_OBJECT_0+索引;等所有对象:WAIT_OBJECT_0;超时:WAIT_TIMEOUT |
MsgWaitForMultipleObjects | 等待多个核心对象激发 同时监听消息 | 1~MAXIMUM_WAIT_OBJECTS(64个) | 支持(通过dwWakeMask) | GUI程序等待(如后台打印时兼顾UI响应) | 比前者多“dwWakeMask”(如QS_ALLINPUT监听所有输入消息) | 对象激发:WAIT_OBJECT_0+索引;消息到达:WAIT_OBJECT_0+对象数;超时:WAIT_TIMEOUT |
SetTimer /KillTimer | 定时触发WM_TIMER消息(GUI程序) | 无(基于消息队列) | 依赖GUI消息循环 | GUI程序定时任务(如界面更新、周期性检查) | SetTimer 需传入“窗口句柄”“定时器ID”“定时周期” | SetTimer 返回非0为成功;KillTimer 无返回值(成功即生效) |
📣 结语
感谢你耐心看完,恭喜你比昨天的你进步了一点点哦!
本文档严格围绕《Win32多线程程序设计.pdf》第三章展开,从“忙等痛点”到“高效等待函数”,再到不同场景(定时、多对象、GUI)的等待方案,所有知识点均来自指定文档,代码案例未做修改,比喻保留以帮助理解。
如果你觉得我写的不错,记得给我点赞,收藏 和 关注哦(。・ω・。)
让我们一起加油,向美好的未来奔去。让我们从一无所知的新手逐渐成为专家。为自己点赞吧!