翻译 异步I/O不会创建新的线程

异步I/O不会创建新的线程

本文翻译自 Stephen Cleary 的 [There is No Thread] 原文地址 https://blog.stephencleary.com/2013/11/there-is-no-thread.html

这是异步编程最基本的事实 : 异步I/O不会创建新的线程

反对这个事实的人很多,他们对此的看法是 "如果我await一个操作,那么一定会有一个线程正在等待,它可能是一个线程池的线程,或者是操作系统线程,或者是某个设备的驱动程序"

不用关注这些看法, 只需要记住如果异步操作是纯粹的(方法全是async),那么就不会产生新的线程

不过这么一个简单的结论显然并不能够让他们信服,下面就看看异步到底发生了什么

让我们跟踪一个异步操作直到硬件层面,注:Net部分和设备驱动部分将被简化描述 (因为细节太多了)

下面是一个通用的写入操作 (例如 文件、网络、USB面包机等等)

private async void Button_Click(object sender, RoutedEventArgs e)
{
  byte[] data = ...
  await myDevice.WriteAsync(data, 0, data.Length);
}

我们已经知道在await的时候UI线程不会被阻塞,那么问题来了: 是否是UI线程创建了另外一个线程杀了祭天换来了自己的自由....

关于UI线程是否犯下这一恶行,让我们深入推断一下

首先 : 看看类库 (BCL的代码) 假设 WriteAsync 是使用.Net中标准的异步方式平台调用(P/Invoke)I/O系统,这是一个基于异步方式的I/O,所以这会在设备的底层上启动一个win32异步方式I/O操作句柄

然后操作系统会告诉设备驱动程序进行写操作,代码实现是构造出表示写请求的对象,这个对象称为I/O请求包(IRP)

设备驱动程序接收到IRP后会向设备发出一个命令来写出数据,如果这个设备支持直接内存存取技术(DMA),只需将缓冲区地址写入设备寄存器即可,设备驱动程序能做到将IRP标记为 "pending" 后交把控制权交还操作系统

832799-20180808135659046-1369102890.png

在这里发现了真相的核心所在 : 在处理IRP时,设备驱动是不允许阻塞的。这意味着如果IRP不能立即完成,那么它必须异步处理,即使对于同步的api也是如此!在设备驱动级别,所有(有意义)的请求都是异步的

引用书籍里的知识 "无论是什么类型的I/O请求,应用程序向驱动程序发出的I/O操作都是异步执行的"

在IRP为 "pending" 时,操作系统返回到类库,类库将未完成的任务返回到应用程序的按钮单击事件方法,该方法挂起async方法,UI线程继续执行

我们已经跟踪请求到系统的尽头 直到物理设备

现在写入操作正在飞快的进行中,有多少个线程正在处理它?

答案是没有

没有设备驱动程序线程、操作系统线程、BCL线程或者写在处理写操作的线程池线程(?) 这里没有创建任何新的线程

现在,让我们跟踪内核守护进程的响应回到正常的人类世界(被大神虐了)

写入请求开始后的某个时间,设备完成了写入操作。它会举手报告中断一下CPU

设备驱动程序的中断服务程序 (ISR) 对中断操作出响应,中断是CPU级事件,它会临时从正在运行的线程中夺取CPU的控制权。可以将ISR看作 "借用" 当前正在运行的线程,但我(原作者)更倾向于认为ISR非常低的级别上运行,以至于 "线程" 的概念还不存在 - 因此可以这么说,它们在线程的下方出现(在线程之前就有了ISR)

无论如何,ISR是正确的,因此它所做的就是告诉设备“谢谢您的中断”,并对延迟过程调用(DPC)进行排队

当CPU成功被中断吸引注意力时,它会到DPCs(注: Distributed Process Control System 分布式处理控制系统),DPCs也是在一非常低的级别中运行(非常底层),说是它是"线程"并不完全正确,与ISR一样,DPCs直接在CPU上运行,在线程系统的"下方"

DPC会把表示写请求的IRP标记为 "complete" ,但是 "completion"状态仅存在于操作系统级别,进程有自己的内存空间所以我们必须通知到它,因此操作系统将一个特殊内核态的异步过程调用(Asyncroneus Procedure Call 简称 APC) 排队到拥有句柄的线程

由于类库/BCL使用了标准的异步方式平台调用(P/Invoke)系统,所以它已经注册了带有I/O输出完成端口(ICOP 注:I/Q Completion Port)的句柄,IOCP是线程池的一部分,因此简单的借用I/O线程池线程来执行APC,APC通知Task已经完成

该Task已捕获UI上下文,因此它不会直接在线程池线程上恢复异步方法,相反,它将该方法排队到UI上下文中,UI线程会恢复执行该方法

因此,我们看到在请求在被处理时没有创建新的线程,当请求完成后,各种线程被"借用",或者有一些处理被简单的排队等候,这个处理通常是在毫秒级时间内完成 (例如 在线程池中运行的APC),甚至到微秒级(例如 ISR),但是没有线程被阻塞,只是等待该请求完成
832799-20180808135714272-532507082.png

我们在上面的样例中使用了被简化后最标准的方式,在实际开发中会比这个样例复杂很多,但是核心的事实是一样的

"必须有一个处理异步操作的线程" 并不是事实

打破常规,不要识图去找到这个 "异步线程",这是不可能的。相反,要去寻找真相:

没有线程

转载于:https://www.cnblogs.com/LiangSW/p/9442354.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值