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

8 事件等待

临界区&自旋锁

这两个章节在”多核同步“篇已经学习过了,需要了解的可以自行查看对应章节。

线程等待与唤醒

我们在之前的课程里面了解了如何自己实现临界区以及什么是Windows自旋锁,这两种同步方案在线程无法进入临界区时都会让当前线程进入等待状态。

一种是通过Sleep函数实现的,一种是通过让当前的CPU”空转“实现的,但这两种等待方式都有局限性:

  1. 通过Sleep函数进行等待,并没有办法确定具体等待的时间,有可能出现等待过长或过短的情况;

  2. 通过让CPU”空转“进行等待,只有在等待事件很短的情况下才有意义,否则空转时间过长,对CPU资源来说就是一种浪费,并且自旋锁的方式只在多核的环境下存在

我们发现如上所说的两种方案,都是由于等待条件的不成熟,所产生的局限。

等待与唤醒机制

在Windows中,一个线程可以通过等待一个或者多个可等待对象,从而进入等待状态,另一个线程可以在某些时刻唤醒等待这些对象的其他线程,这就是Windows的等待与唤醒机制

images/download/attachments/2424889/image2023-5-15_15-45-45.png

可等待对象

所谓可等待对象就是结构体,如下是一些结构体,我们可以在Windbg中查看这些结构体。

images/download/attachments/2424889/image2023-5-15_17-21-41.png

在Windbg中查看结构体,我们会发现这些结构体的第一个成员都是_DISPATCHER_HEADER,也就表示第一个成员为_DISPATCHER_HEADER的结构体就是可等待对象。

images/download/attachments/2424889/image2023-5-15_17-23-29.png

除了这个以外,在Windows中还有一些特殊的结构体,也称之为可等待对象,如_FILE_OBJECT,我们可以看见该结构体的第一个成员就不是_DISPATCHER_HEADER,但是在它的0x5C偏移位成员有一个_KEVENT结构体,这个结构体是一个可等待对象,因此_FILE_OBJECT也是一个可等待对象。

images/download/attachments/2424889/image2023-5-15_17-26-32.png

因此,综上所述我们可以知道只要结构体中成员有_DISPATCHER_HEADER,或包含了_DISPATCHER_HEADER结构体的,我们都可以称之为可等待对象

差异

虽然以上所述的两种类型结构体都称之为可等待对象,但两者之间也是有差异的。差异在等待函数调用过程中体现出来。

当我们使用WaitForSingleObject函数时,进入内核函数NtWaitForSingleObject,这个内核函数会通过3环用户提供的句柄找到等待对象的内核地址;然后判断等待对象的第一个成员是否是_DISPATCHER_HEADER,如果是的话则直接使用;如果不是的话,则去等待对象中找到嵌入的_DISPATCHER_HEADER对象。最后再将找到的对象地址作为参数调用KeWaitForSingleObject函数,该函数核心功能会在后续章节中学习。

images/download/attachments/2424889/image2023-5-15_17-36-59.png

等待块

一个线程可以等待一个或多个对象,线程与等待对象建立联系主要通过等待块,我们可以做个实验来看一下。

一个线程等待一个对象

首先我们可以在XP中编译这样一段代码,来看一下一个线程等待一个对象的情况:

#include <windows.h>

#include <stdio.h>

HANDLE hEvent[2];

DWORD WINAPI ThreadProc(LPVOID lpParameter)

{

::WaitForSingleObject(hEvent[0], -1);

printf("ThreadProc函数执行...\n");

return 0;

}

int main(int argc, char* argv[])

{

hEvent[0] = ::CreateEvent(NULL, TRUE, FALSE, NULL);

::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL);

getchar();

return 0;

}

运行程序之后,在Windbg中断一下,然后通过如下图中的指令找到当前进程中正在等待的线程地址:

images/download/attachments/2424889/image2023-5-16_16-33-2.png

接着我们以线程结构体的方式代入线程地址查看,找到0x5C偏移位成员,即等待块:

images/download/attachments/2424889/image2023-5-16_16-36-7.png

等待块是一个_KWAIT_BLOCK结构体,它将线程与被等待对象联系到了一起,我们接着来看一下它的成员含义:

nt!_KWAIT_BLOCK

+0x000 WaitListEntry : _LIST_ENTRY // 稍后了解

+0x008 Thread : Ptr32 _KTHREAD // 当前线程地址

+0x00c Object : Ptr32 Void // 等待对象的地址(当前实验中为_KEVENT)

+0x010 NextWaitBlock : Ptr32 _KWAIT_BLOCK // 下一个等待块地址,这是一个单向循环链表,存储的是与当前线程关联的多个等待块结构体地址,如果只有一个等待块则该地址指向当前等待快地址

+0x014 WaitKey : Uint2B // 等待块的索引,当前为第一个等待块,因此该值为0

+0x016 WaitType : Uint2B // 等待类型,若当前只要有一个等待对象符合条件就可以使得线程被唤醒,那么该值就是1;如果你等待多个对象必须全部符合条件才可以使得线程被唤醒,那么该值0

images/download/attachments/2424889/image2023-5-16_16-37-23.png

综上所述,我们可以使用如下图来表示一个线程等待一个对象的情况:

images/download/attachments/2424889/image2023-5-16_16-43-26.png

一个线程等待多个对象

一个线程等待多个对象的情况,我们需要将代码进行修改,如下代码所示,我们添加两个可等待对象,然后将WaitForSingleObject替换为WaitForMultipleObjects,这样就可以使得一个线程等待多个对象。

值得注意的是,WaitForMultipleObjects函数多出了两个参数,分别是第一个参数nCount(即等待对象的数量)第二个参数bWaitAll


http://www.kler.cn/a/500338.html

相关文章:

  • ubuntu22.04 的录屏软件有哪些?
  • Windows下调试Dify相关组件(1)--前端Web
  • 2024版idea 插件无法加载
  • C#中通道(Channels)的应用之(生产者-消费者模式)
  • 微商关系维系与服务创新:链动2+1模式、AI智能名片与S2B2C商城小程序的应用研究
  • AI刷题-数列推进计算任务、数组中的幸运数问题
  • 【复习小结】1-13
  • [ Spring ] Install MongoDB on Ubuntu24
  • windows及linux 安装 Yarn 4.x 版本
  • 记录一个在增量更新工具类
  • SpringBoot操作spark处理hdfs文件
  • 第432场周赛:跳过交替单元格的之字形遍历、机器人可以获得的最大金币数、图的最大边权的最小值、统计 K 次操作以内得到非递减子数组的数目
  • IDEA中创建maven项目
  • Redis之秒杀活动
  • django基于Python的智能停车管理系统
  • 限制图层列表
  • (2025,Cosmos,世界基础模型 (WFM) 平台,物理 AI,数据处理,分词器,世界基础模型预训练/后训练,3D一致性)
  • 【JVM-1】深入解析JVM:Java虚拟机的核心原理与工作机制
  • 【MySQL学习笔记】MySQL视图View
  • 解决nginx多层代理后应用部署后访问发现css、js、图片等样式加载失败
  • CPU缓存架构详解与Disruptor高性能内存队列实战