当前位置: 首页 > news >正文

【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:核心参数,文档中两种用法:
    1. 任务执行中(i>3):设为FALSE,仅等“任意一个线程结束”,避免盲目等待;
    2. 程序末尾:设为TRUE,一次等待所有剩余线程结束,简化代码;
  • 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)

TaskQueMWaitForMultipleObjects替代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)传统等待函数的缺陷

若用WaitForSingleObjectWaitForMultipleObjects等待核心对象,主线程会陷入“等对象激发”的循环,无法调用GetMessage处理消息——直接导致UI卡死。

2. 解决方案:MsgWaitForMultipleObjects函数(文档核心)

该函数支持“同时等待核心对象激发或消息到达”,相当于“既能等信号灯变绿,又能听闹钟(消息)”,兼顾等待与UI响应。

(1)函数原型与关键参数
DWORD MsgWaitForMultipleObjects( DWORD nCount,        // 待等待的核心对象个数LPHANDLE pHandles,   // 核心对象句柄数组(如线程句柄)BOOL fWaitAll,       // 等待策略:TRUE=等所有;FALSE=等任意一个DWORD dwMilliseconds,// 超时时间(INFINITE=无限等)DWORD dwWakeMask     // 需监听的消息类型(核心新增参数)
);
  • dwWakeMask:指定“哪些消息会唤醒函数”,文档中常用取值:
    1. QS_ALLINPUT:监听所有输入消息(键盘、鼠标、定时器等);
    2. QS_PAINT:仅监听窗口重绘消息;
    3. 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 函数对比表

对比维度WaitForMultipleObjectsMsgWaitForMultipleObjects
核心用途等待多个核心对象(如线程、事件)激发,不处理消息等待多个核心对象激发 同时监听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)的等待方案,所有知识点均来自指定文档,代码案例未做修改,比喻保留以帮助理解。

如果你觉得我写的不错,记得给我点赞,收藏 和 关注哦(。・ω・。)

让我们一起加油,向美好的未来奔去。让我们从一无所知的新手逐渐成为专家。为自己点赞吧!

http://www.dtcms.com/a/490654.html

相关文章:

  • CentOS 7 FTP安装与配置详细介绍
  • 网页设计跟网站建设的区别淘宝店铺运营推广
  • 机器学习使用GPU
  • 做网站分为哪些功能的网站找工作网
  • 湖南粒界教育科技有限公司:专注影视技能培养,AI辅助教学提升学员就业竞争力
  • 【系统分析师】写作框架:静态测试方法及其应用
  • React useEffect组件渲染执行操作 组件生命周期 监视器 副作用
  • 在哪些场景下适合使用 v-model 机制?
  • 长沙申请域名网站备案查域名服务商
  • 游标卡尺 东莞网站建设大连建设工程信息网去哪里找
  • 华为USG防火墙之开局上网配置
  • 【第五章:计算机视觉-计算机视觉在医学领域中应用】1.生物细胞检测实战-(3)基于YOLO的细胞检测实战:数据读取、模型搭建、训练与测试
  • 【MFC实用技巧】对话框“边框”属性四大选项:None、Thin、Resizing、对话框外框,到底怎么选?
  • 网站备案 备注关联性天津网站建设内容
  • 所有网站收录入口济南市住监局官网
  • frida android quickstart
  • 作为测试工程师,我们该如何应用 AI?
  • 【Flutter】Flutter项目整体架构
  • 电子电气架构 --- 未来汽车软件架构
  • 怎么优化网站关键词辽宁省住房建设厅网站科技中心
  • 电力自动化新突破:Modbus如何变身Profinet?智能仪表连接的终极解决方案
  • cGVHD患者的血常规指标 生化指标 动态监测
  • 重庆网站建设师网站顶部布局
  • 【算法与数据结构】二叉树后序遍历非递归算法:保姆级教程(附具体实例+可运行代码)
  • AI-调查研究-105-具身智能 机器人学习数据采集:从示范视频到状态-动作对的流程解析
  • 基于 PyQt5 的多算法视频关键帧提取工具
  • 企业手机网站建设有wordpress download 插件
  • 【EE初阶 - 网络原理】应用层协议(上)
  • 2025国际集成电路展览会暨研讨会有那些新技术与亮点值得关注?
  • 【图片处理】✈️HTML转图片字体异常处理