异步(非阻塞)代码的可伸缩性优势是什么?

时间:2016-01-14 20:27:14

标签: c# multithreading asynchronous scalability

由于以下两个主要原因,阻止线程被认为是一种不好的做法:

  1. 线程耗费内存。
  2. 线程通过上下文切换来节省处理时间。
  3. 以下是我遇到这些原因的困难:

    1. 非阻塞,异步代码也应该花费相同数量的内存,因为在执行异步调用之前应该将callstack保存在某处(上下文 保存,之后所有)。如果线程效率非常低(内存方面),为什么OS / CLR不提供更轻量级的线程(只保存callstack的上下文而不是其他内容)?难道它不是一个更清洁的内存问题解决方案,而不是强迫我们以异步方式重新构建我们的程序(这更复杂,更难理解和维护)?

    2. 当线程被阻塞时,操作系统将其置于等待状态。操作系统不会上下文切换到休眠线程。由于超过95%的线程生命周期花费在休眠上(假设这里有IO绑定的应用程序),性能命中应该可以忽略不计,因为线程的处理部分可能不会被操作系统抢占,因为它们应该跑得很快,做得很少。因此,在性能方面,我无法看到非阻塞方法带来很多好处。

    3. 我在这里缺少什么,或者为什么这些论点存在缺陷?

2 个答案:

答案 0 :(得分:16)

  

非阻塞,异步代码也应该花费相同数量的内存,因为在执行异步调用之前应该将callstack保存在某处(毕竟保存了上下文)。

发生await时,整个调用堆栈保存。为什么你认为需要保存整个调用堆栈? 调用堆栈是继续的实现等待任务的延续不是 await 的延续。 await的延续是在堆栈上。

现在,情况很可能是当等待给定调用堆栈中的每个异步方法时,等效于调用堆栈的信息已存储在每个任务的延续中。但是这些延续的内存负担是垃圾收集堆内存,而不是一百万字节的已提交堆栈内存。连续状态大小是任务数量的大小n;无论你是否使用它,线程的负担都是一百万字节。

  

如果线程效率非常低(以内存方式),为什么OS / CLR不能提供更轻量级的线程

操作系统确实如此。它提供纤维。当然,纤维仍然有堆叠,所以这可能不会更好。我想你可以有一个小筹码的线程。

  

对于内存问题,它不是一个更清洁的解决方案,而不是强迫我们以异步方式重新构建我们的程序

假设我们制作线程 - 或者就此而言,流程 - 便宜得多。这仍然无法解决同步访问共享内存的问题。

对于它的价值,我认为如果工艺重量更轻,那将是很好的。他们不是。

此外,这个问题有点自相矛盾。您正在使用线程,因此您已经愿意承担管理异步操作的负担。给定的线程必须能够在产生第一个线程要求的结果时告诉另一个线程。线程已经暗示异步,但异步并不意味着线程化。拥有内置于语言,运行时和类型系统的异步体系结构,只会使不幸的人必须编写管理线程的代码。

  

由于超过95%的线程生命周期花在休眠上(假设这里有IO绑定的应用程序),性能命中应该可以忽略不计,因为线程的处理部分可能不会被操作系统抢占因为他们应该跑得很快,做很少的工作。

为什么你会雇佣一个工人(线程)并支付他们的工资来坐在等待邮件到达的邮箱(睡觉线程)(处理IO消息)? IO中断首先不需要线程。 IO中断存在于线程级别以下的世界中。

不要雇佣一个线程来等待IO;让操作系统处理异步IO操作。雇佣线程来做疯狂的大量高延迟CPU处理,然后为你拥有的每个CPU分配一个线程。

现在我们来回答您的问题:

  

异步(非阻塞)代码有什么好处?

  • 不阻止UI线程
  • 更容易编写生活在高延迟世界的程序
  • 更有效地利用有限的CPU资源

但是,让我用类比来重新解释这个问题。你在经营一家快递公司。有许多订单进入,许多交货已经结束,您无法告诉客户您在他们完成之前的每次交货之前都不会收货。哪个更好:

  • 雇用五十个人接听电话,领取包裹,安排送货和送货包裹,然后要求其中46人随时闲置

  • 雇佣四个人,让他们每个人非常好,一次做一点工作,这样他们总是能够响应客户的要求,其次,真的擅长保留将来需要完成的工作清单

后者对我来说似乎更好。

答案 1 :(得分:-2)

您在这里弄乱multithreadingasync概念。

您的“困难”都来自于假设每个async方法都被分配了一个专门的线程,它可以在其上完成工作。但是,事态是完全相反的:每次需要执行async操作时,CLR从线程池中选择一个空闲(因此已经创建)的线程并在所选线程上执行该方法< /强>

这里的核心概念是async并不意味着总是创建新线程,它意味着在现有线程上调度执行,以便没有线程处于空闲状态。