专栏首页Eureka伽罗的技术时光轴Windows APC机制 & 可警告alertable的线程等待状态

Windows APC机制 & 可警告alertable的线程等待状态

摘要:Windows APC的全称为(asynchronous procedure call)翻译为中文即“异步过程调用”。《Windows APC机制(一)》、《谈谈对APC的一点理解》、《线程的Alertable与User APC》主要阅读了这三篇文章,对APC有了个大概了解:

1) APCs允许用户程序和系统元件在一个进程的地址空间内某个线程的上下文中执行代码。

2) I/O管理器使用APCs来完成一个线程发起的异步的I/O操作。例如:当一个设备驱动调用IoCompleteRequest来通知I/O管理器,它已经结束处理一个异步I/O请求时,I/O管理器排队一个APC到发起请求的线程。然后线程在一个较低IRQL级别,来执行APC。 APC的作用是从系统空间拷贝I/O操作结果和状态信息到线程虚拟内存空间的一个缓冲中。

3) 使用APC可以得到或者设置一个线程的上下文和挂起线程的执行。

谈到APC,不可避免的牵涉到QueueUserAPC函数——“QueueUserAPC函数把一个APC对象加入到指定线程的APC队列中。”从函数名称,也应该能推测到一个线程其实有两个APC队列:用户APC、系统APC。

Windows APC函数是被按照先进先出(FIFO)顺序放置在一个队列Queue上面的。同时,用户APC函数极为特别,它只有在线程处于“可警告alertable的线程等待状态”时才能被线程调用。但是,线程一旦开始调用APC函数,就会一次性将所有APC队列上的函数全部执行完毕。

那么,什么是可警告alertable的线程等待状态?其实就是线程暂时没有重要的事情要做,就叫做这个状态。APC函数一般不会去干扰(中断)线程的运行,从上文中知道,一个线程附带着两个APC队列(用户APC、系统APC),也就相当于这两个队列的APC函数都是由“线程本身”来储备调用的(APC函数就相当于奥运会比赛上的预备选手),只有当线程处于“可警告的线程等待状态”才会去调用APC函数(比赛时只有主将无法上场时,预备选手才会出现)。

如何衡量线程此时是否有重要的事情要做?对于用户模式下,可以调用函数SleepEx、SignalObjectAndWait、WaitForSingleObjectEx、WaitForMultipleObjectsEx、MsgWaitForMultipleObjectsEx都可以使目标线程处于alertable等待状态(无重要事情要做),从而让用户模式APCs执行,原因是这些函数最终都是调用了内核中的KeWaitForSingleObject,KeWaitForMultipleObjects,KeWaitForMutexObject,KeDelayExecutionThread等函数。但是这里需要注意的是线程执行Sleep(10)函数时,并不是“可警告alertable的线程等待状态”。想象一个应用场景:客户端程序每隔5分钟就和服务端进行一次通信,实现“心跳”,最简单的就是使用Sleep(5*60*1000)。那么这样一来,这5分钟内,线程就沉睡了,如果这个时候有比较紧急的网络IO事件发生怎么办呢?线程还在沉睡中,因为5分钟时间还未到,所以无法及时处理这些事件。如何解决这个问题呢?那就是使用SleepEx替换Sleep。这个函数比起Sleep就多了一个参数Alertable,表示该线程是“可唤醒的”,就是说,线程虽然等待时间未到,但如果发生一些事件,线程也会及时去处理。这些事件就是:IO完成例程需要执行或者线程有APC需要交付。

对于上述说明,抽取其中的SleepEx作为例子简单介绍:

SleepEx( _In_ DWORD dwMilliseconds, _In_ BOOL bAlertable );

dwMilliseconds:等待时间,以毫秒为单位。如果该值为INFINITE值,则表示无限等待下去;

bAlertable:函数返回方式。如果为FALSE,除非该函数调用超时,否则该函数不返回。在此期间如果IO完成了回调,完成例程也不会被执行。如果为TRUE,当该函数调用超时或者IO完成回调时,该函数都会返回——当调用超时时,该函数返回WAIT_OBJECT_0(亦即0);如果返回IO完成回调才返回的话,则返回值为WAIT_IO_COMPLETION。

接下来,举个APC的实例:

在实例中需要注意三处:①如果APC函数在线程启动前就已经注入了,那么线程将会在启动前——将所有已经注入的APC函数全部执行完毕,才真正执行线程体;②main函数中之所以要使用Sleep(1),是为了让线程跑起来以后再执行APC函数。否则,如果所有APC函数都执行完毕了线程才真正跑起来,这时候进入SleepEx无限等待中,而没有APC例程去触发它。线程将会卡死在SleepEx处。③当线程退出后,再加入APC函数将不会被执行,因为线程体都已经销毁了。

#include <stdio.h> #include <Windows.h> #include <process.h>//_beginthreadex VOID NTAPI before_thread_running(ULONG_PTR param) { printf("apc1.\n"); } VOID NTAPI thread_running(ULONG_PTR param) { printf("apc2.\n"); } VOID NTAPI after_thread_running(ULONG_PTR param) { printf("apc3.\n"); } unsigned int __stdcall sub_thread(void* context) { printf("sub_thread begin.\n"); DWORD ret = SleepEx(INFINITE, TRUE); switch (ret) { case WAIT_OBJECT_0: //Time Out printf("WAIT_OBJECT_0\n"); break; case WAIT_IO_COMPLETION: //IO完成,强制退出SleepEx printf("WAIT_IO_COMPLETION\n"); break; default: break; } printf("sub_thread end.\n"); return 0; } int main(int argc, char* argv[]) { HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, sub_thread, NULL, 0, NULL); QueueUserAPC(before_thread_running, hThread, NULL);//APC queue(FIFO) Sleep(1); QueueUserAPC(thread_running, hThread, NULL); Sleep(1); QueueUserAPC(after_thread_running, hThread, NULL); WaitForSingleObject(hThread, INFINITE);//wait sub_thread CloseHandle(hThread); return 0; }

apc1. sub_thread begin. apc2. WAIT_IO_COMPLETION sub_thread end.

@qingdujun

2017-7-28 in Xi'An ———————————————— 版权声明:本文为CSDN博主「qingdujun」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qingdujun/article/details/76223282

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • windows 常用thread方法

    1.HANDLE CreateThread( _In_opt_LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_S...

    战神伽罗
  • 如何解决在DLL的入口函数中创建或结束线程时卡死

    通常情况下并不会使用到DLL的MAIN函数,因为delphi的框架已经把Main函数隐藏起来

    战神伽罗
  • VCL线程的同步方法 Synchronize(用消息来同步)

    RTL(Run-Time library),运行时库,包括System、SysUtils、Math三个单元,提供的函数与语言、编译器、操作系统及进程有关

    战神伽罗
  • 教面试官ReentrantLock源码

    学习完 AQS,本文我们就来研究第一个 AQS 的实现类:ReentrantLock。

    JavaEdge
  • ReentrantLock 核心源码解析

    学习完 AQS,本文我们就来研究第一个 AQS 的实现类:ReentrantLock。

    JavaEdge
  • Java不可重入锁和可重入锁理解

    最近正在阅读Java ReentrantLock源码,始终对可重入和不可重入概念理解不透彻,进行学习后记录在这里。

    哲洛不闹
  • Java锁

    乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更...

    葆宁
  • 《面试补习》- Java锁知识大梳理

    悲观锁,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

    九灵
  • 这篇 ReentrantLock 看不懂,加我我给你发红包

    在开始本篇文章的内容讲述前,先来回答我一个问题,为什么 JDK 提供一个 synchronized 关键字之后还要提供一个 Lock 锁,这不是多此一举吗?难道...

    cxuan
  • 如何面试前端工程师:GitHub很重要

    我在阿里巴巴的一部分工作内容是面试前端工程师。其实关于面试你可能很有自己的一套,这里我想跟你们分享一下我常用的方法。

    前端老鸟

扫码关注云+社区

领取腾讯云代金券