异步编程,线程和效率

时间:2016-12-15 21:02:24

标签: c# asp.net async-await

我的问题不仅适用于C#和Asp.net,但我更容易提出更具体的问题。

当Asp.net请求等待异步IO操作时,该线程基本上进入线程池以供其他请求重用。为什么这比直到IO操作完成之前休眠线程更有效?毕竟,当线程返回到线程池时,其堆栈需要保留在内存中以完成原始请求。我的假设是我们不能重用分配给线程的内存,除非我们在其他地方复制使用过的堆栈内存并且复制数据可能会引入额外的开销,这可能是不合理的。

我错过了什么吗?我的假设是错的吗?请解释一下。

编辑:jlew指出的答案缺少一点。当线程返回池时,请求使用的堆栈内存会发生什么?如果我们不能重用内存,那么重用线程有什么意义呢?如果我们想重用未使用的堆栈部分,那么我们将不得不移动一些内存。如果是这样,移动内存并重新使用未使用的堆栈内存可以提高整体效率吗?

1 个答案:

答案 0 :(得分:10)

  

为什么这比直到IO操作完成之前暂停线程更有效?

将线程视为工作者。工人很贵。你想付工人睡觉吗?不,你想付给工人尽可能多的工作;如果他们被阻止,你会让他们在其他事情上工作而不是睡觉,直到堵塞被清除。

线程非常昂贵。如果线程便宜那么肯定,我们可以赚很多钱。您只对昂贵的资源使用汇集策略。

线程主要有两种方式:代价是堆栈的大小,即每个线程1MB的预留虚拟内存。但是,操作系统级别也存在大量成本,而这些成本并未针对具有数千个线程的方案进行优化。确定接下来要运行的线程,上下文切换到它,以及切换它,所有这些都具有非零成本,随着线程数量的增加而增加。理想情况下,您需要n个处理器计算机中的n个独立线程,不多也不少。

  

毕竟,当线程返回到线程池时,需要将其堆栈保存在内存中以完成原始请求。

我无法说出这句话的正面或反面。当线程返回池时,其堆栈指针返回到底部。

  

我的假设是我们不能重用分配给线程的内存,除非我们在其他地方复制使用过的堆栈内存并且复制数据可能会引入额外的开销,这可能是不合理的。

我开始理解它。你的心理模型是:

  • stack是continuation(我们接下来要做什么?)和激活(这个方法激活中locals的值是什么?)的具体化。
  • 等待完成的任务必须在他们离开的工作流程中继续,本地人不变
  • 因此,必须在某处或其他地方复制继续/激活信息 - 堆栈。

这种心智模式似乎有道理但却错了。异步工作流构建状态机并将延续包作为该机器中的状态,然后在任务中存储对状态机的引用。激活信息从堆栈槽提升到闭包类的字段。这会将连续/激活信息从堆栈中移入堆中。

现在,请记住,任务的继续不包含堆栈中的所有信息;它不是“当前持续调用”意义上的真正延续,它捕获了当前方法完成时发生的事情。它捕获当前等待的任务完成时发生的事情,这就足够了。

请记住,当“下一步我要做什么?”时,堆栈只能用作延续的具体化。工作流在逻辑上是一个堆栈 - 你接下来做的事情是堆栈顶部的东西。但异步工作流形成了依赖关系树,其中连接点正在等待;它首先不是堆栈,因此将延续表示为堆栈是非启动的。

  

当线程返回池时,请求使用的堆栈内存会发生什么变化?

只要线程继续存在,为线程堆栈保留的百万字节虚拟内存就会保留。这很重要!这就是为什么线程在.NET中如此昂贵的一个原因。就操作系统内存管理器而言,1MB的虚拟内存100%正在使用,无论该堆栈被推到多少。大多数1MB的大部分时间都是垃圾。

当线程回到池中时,指向该堆栈的高水位线的指针会移回到开头,而超出它的所有内容现在都是垃圾。

  

如果我们不能重用内存,那么重用线程有什么意义呢?

我们可以并且重复使用内存;堆栈指针被重置。

  

如果是这样,移动内存并重新使用未使用的堆栈内存可以提高整体效率吗?

我没有按照这个问题的主旨;我怀疑这是基于错误的前提。你能改一下吗?