这里没有线程

原文地址:https://blog.stephencleary.com/2013/11/there-is-no-thread.html

前言

我是在看 C#8.0 新特性异步流时在评论里看到这篇文章的,阅读之后发现这篇文章干货满满,作者解释的非常清晰,里面的本质分析内容在《CLR via C#》一书中也有讲到。更加加深了我的印象。遂在这里翻译过来,以便加深自己的理解

正文

一个本质的事实就是纯粹的异步是不会产生线程的

反对这个事实的人有很多。“不”,他们喊道:“如果我正在等待一个操作,那么这个线程就必须在执行等待!它可能是线程池线程。或者是一个操作系统(OS)线程,又或是其他设备驱动程序...”

无需理会他们。如果一个异步操作是纯粹的(pure),那么就不会有线程的。

那些持怀疑态度的人。那我们就迎合他们罢。

我们将跟踪一个异步操作一直到硬件,特别是 .net 部分和硬件部分。我们必须通过省略一些中间的细节来简化描述,但是我们不能偏离事实真相。

通常写一个异步操作(文件,网络流,USB 接口等等)。代码如下:

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

我们已经知道在 await 的时候 UI 线程是不会阻塞的。那么问题来了:这里有没有是其他线程在阻塞期间牺牲自己以至于让 UI 线程存活呢?

那让我们继续往下深究

第一步:库(比如进入到 BCL 库源代码)。我们假使 WriteAsync 是通过 [http://msdn.microsoft.com/en-us/library/system.threading.overlapped.aspx](P/Invoke 在 .net 异步的标准实现) 来实现的,它是基于overllapped I/O 的。所有它在一个设备驱动程序的句柄上开始一个 Win32 overlapped 操作。

OVERLAPPED 是一个包含了用于异步输入输出的信息的结构体;详细解释移步 https://en.wikipedia.org/wiki/Overlapped_I/O

操作系统然后就会转向设备驱动程序并开始请求一个写操作。它首先会构造一个表示写请求的对象;它被称为 I/O Request Packet(IRP)。

设备驱动程序接受到 IRP 之后并向设备提交一个写出数据的命令。如果这个设备支持直接内存访问(DMA 全称是 Direct Memory Access),这能够像写到缓冲区到寄存器一样简单。这就是设备驱动程序所做的一切;它把 IRP 标记为 "pending" (挂起) 并返回给操作系统。

本质核心就在这里:设备驱动程序正在处理 IRP 时是不会允许阻塞的。也就是说如果 IRP 不能马上完成,那么它必须要异步处理。甚至是同步 API 也是如此!在设备驱动程序级别,所有的请求(重要的)都是异步的。

这里引用了 Tomes知识,“无论I/O请求的类型如何,在内部,代表应用程序向驱动程序发出的I/O操作都是异步执行的”

在 IRP 挂起的时候,OS 返回库,库返回了一个未完成的任务给按钮点击事件,并暂停了 async 方法, UI 线程继续执行。

我们跟着请求继续往下走,现在到达了设备的物理层

现在写操作正在进行。那么有多少线程正处理它呢?

没有。

这里没有设备驱动程序线程、OS 线程、库(BCL)线程或者是线程池线程操作写操作。这里没有线程

现在我们来跟着从来自内核的相应回到最初的世界。

在开始写请求之后的一段时间,设备完成了写操作。它会以中断的方式来通知 CPU。

设备驱动程序的中断服务程序(ISR(Interrupt Service Routine) )响应中断。这个中断是 CPU 级别的事件,无论哪个线程正在运行都会临时的抢占 CPU 的控制权。你可以认为 ISR 是在“借”当前正在运行的线程,但是我更倾向于 ISR 运行时的级别非常低,以至于不存在“线程”的概念。可以这么说,它们在所有线程之下进来的。

不管怎样,ISR 正确写完了,完了它会通知设备程序 “谢谢你的中断” 并且进入 DPC(Deffered Procedure Call) 队列(延迟过程调用)

当 CPU 被中断干扰时,它将会到达 DPCs。DPCs 也会执行在一个很低的级别以至于说它是一个线程是不正确的;就像 ISRs,DPCs 直接在 CPU 上运行,在线程系统之下。

PDC 接受代表写请求的 IRP 并且标记为 “已完成”。然而,这个“完成”状态只存在于 OS 级别;进程有它自己的内存空间,它必须被通知。所以 OS 会入队列一个特殊内核模式异步过程调用(APC)到拥有自己句柄的线程。

由于 BCL 库使用了标准的 P/Invoke overlapped I/O 系统,它已经在 I/O Completion Port(IOCP)注册句柄,它是线程池的一部分。所以借用 I/O 线程池线程来执行 APC,它会通知这些任务已经完成了。

这个任务已经捕捉了 UI 上下文,所以它不会直接在线程池线程上恢复异步方法。而是它将该方法的延续排队到 UI 上下文中,并且 UI 线程将恢复执行那个方法。

所以我们看到,正当一个请求处理时这里是没有线程的。当请求完成时,一些线程被借过去或者是被短暂的排队。这项工作通常在 1 毫秒左右(例如 APC 运行在线程池线程)或 1 微妙左右(例如 ISR)。但是这里没有线程是阻塞的,仅仅只是等待请求完成。

现在,我们遵循的路径是标准路径,这是如此清晰简单。这里有无数的变量,但是核心是不变的。

所以说 “这里必须有一个线程是在处理异步操作” 是不正确的。

释怀吧,不要尝试找到异步线程——这是不可能的。而是你应该去了解真相:

没有线程

该篇文章的评论也是很精彩的,特别是讨论将异步操作当作消息处理那部分讨论,建议也花时间看下

本文同步至:https://github.com/MarsonShine/MarsonShine.github.io/blob/master/mardown/async/There-IS-NO-Thread.md

最新文章

  1. 理解C# 4 dynamic(3) – DynamicObject的使用
  2. Nginx反向代理模板
  3. javascript保留关键字
  4. SQL还原备份数据库读取失败 38错误解决办法
  5. C 语言函数指针
  6. 微软 Office 2010 SP2 正式版下载大全(含简中)
  7. Android -- 从源码的角度一步步打造自己的TextView
  8. R语言实现二分查找法
  9. IIS环境配置和项目部署
  10. Java基本数据类型装箱的127临界点
  11. 迅为4412开发板Linux驱动教程——编写简单应用调用驱动
  12. 1.angular之Hello World
  13. [转]linux下释放文件内存
  14. 用Python开始机器学习(3:数据拟合与广义线性回归)
  15. [Apache] Apache 從 2.2 換至 2.4 httpd.conf 的調整筆記 (windows 環境)
  16. 拼多多商品id怎么查看 拼多多店铺ID怎样看
  17. MVC Dropdownlist数据绑定 默认值
  18. js常用函数汇总(不定期更新)
  19. 程序员之---C语言细节19(来找茬,由/* */ 引起的凝视错误)
  20. 基于C#编程语言的Mysql常用操作

热门文章

  1. 2018-7-31-C#-判断两条直线距离
  2. Python涉及的各个领域以及技术应用
  3. spring的几个面试题
  4. 如何用python“优雅的”调用有道翻译?
  5. VisualStudio 断点调试详解
  6. LDO
  7. 微信群打卡机器人XiaoV项目开源 | 蔡培培的独立博客
  8. JAVA8学习——深入浅出函数式接口FunctionInterface(学习过程)
  9. The first day of Crawler learning
  10. 曹工杂谈--使用mybatis的同学,进来看看怎么在日志打印完整sql吧,在数据库可执行那种