线程与异步执行。有什么不同?

时间:2010-05-22 01:30:09

标签: multithreading performance asynchronous

我相信任何类型的异步执行都会使一个线程处于不可见区域。但如果是的话,

  • 与线程代码相比,异步代码不会提供任何性能提升。

但是我无法理解为什么这么多开发人员正在制作许多异步形式的功能。 你能解释一下它们的差异和成本吗?

4 个答案:

答案 0 :(得分:6)

异步执行的目的是阻止调用异步方法(前台代码)的代码被阻止。这允许你的前台代码继续执行有用的工作,而异步线程是在后台执行您要求的工作。如果没有异步执行,前台代码必须等到后台任务完成才能继续执行。

异步执行的成本与线程上运行的任何其他任务的成本相同。

通常,异步结果对象是使用前台代码注册的。异步结果对象可以在后台任务完成时引发事件,或者前台代码可以定期检查异步结果对象以查看其完成标志是否已设置。

答案 1 :(得分:2)

并发不一定需要线程。

例如,在Linux中,您可以执行非阻塞系统调用。使用这种类型的调用,您可以例如启动许多网络读取。您的代码可以手动跟踪读取(使用列表中的句柄或类似内容),并定期询问操作系统是否有任何连接上的新数据。在内部,操作系统还会保留正在进行的读取列表。使用这种技术,您可以在没有任何(额外)线程的情况下实现并发,无论是在程序中还是在OS中。

如果使用线程并阻止IO,通常每次读取都会启动一个线程。在这种情况下,操作系统将具有正在进行的线程列表,当没有可用时,它会在脚踏板尝试读取数据时停放。当数据可用时,线程将恢复。

在线程之间进行OS切换可能会以上下文切换的形式涉及稍微增加的开销 - 切换程序计数器和寄存器内容。但真正的交易破坏者通常是每个线程的堆栈分配。在Linux上,默认情况下,此大小为几兆字节。如果程序中有 lot 并发,这可能会促使您使用非阻塞调用来处理每个线程的更多并发。

因此可以在没有线程的情况下进行异步编程。如果您只想使用阻塞的OS调用进行异步编程,则需要在继续时专用线程来执行阻塞。但是如果使用非阻塞调用,只需一个线程即可完成大量并发操作。看看Node.js,它支持许多并发连接,同时对大多数操作都是单线程的。

同时查看Golang,它使用一种名为goroutines的绿色线程来实现类似的效果。多个goroutine在同一个OS线程上并发运行,它们在堆栈内存中受到限制,进一步推动了限制。

答案 2 :(得分:1)

  
      
  • 与线程代码相比,异步代码不会提供任何性能提升。
  •   

异步执行是多线程执行的特征之一,随着处理器包含更多内核,这种特性变得越来越重要。

对于服务器而言,多核仅具有轻微的相关性,因为它们已经考虑了并发性并且会自然扩展,但多核与桌面应用程序特别相关,桌面应用程序通常只同时执行一些操作 - 通常只有一个前台任务与后台线程。现在,如果他们要利用多核CPU的强大功能,他们必须被编码为同时做很多事情。

关于性能 - 在单核上 - 异步任务减慢了系统的速度,就像它们顺序运行一样(这是一个简单的,但大多数情况下都是如此。)因此,运行任务A,需要10秒如果B以异步方式运行,则任务B在单个核心上占用5秒,所需的总时间为15秒。原因是,当B运行时,它会从A-A和B中占用cpu资源来竞争相同的cpu。

使用多核计算机,其他任务在其他未使用的核心上运行,因此情况不同 - 额外的任务实际上不会消耗任何时间 - 或者更准确地说,它们不会从核心中消耗时间运行任务A.因此,在多核上异步运行任务A和B将只需10s - 而不是单核的15s。 B的执行与A同时运行,并且在一个单独的核心上运行,因此A的执行时间不受影响。

随着任务和核心数量的增加,性能的潜在改进也会增加。在并行计算中,利用并行性来提高性能被称为speedup

我们已经看到了64核心cpus,我们认为我们将在几年内拥有1024个核心。与单线程同步情况相比,这可能是1024倍的潜在加速。因此,要回答您的问题,使用异步执行显然可以获得性能提升。

答案 3 :(得分:0)

  

我相信任何类型的异步执行都会使一个线程处于不可见区域。

这是你的问题 - 这实际上并非如此。

问题是,您的整个计算机实际上是大规模异步 - 请求RAM,通过网卡进行通信,访问HDD ......这些都是固有的异步操作。

现代操作系统实际上是围绕异步I / O构建的。例如,即使您执行同步文件请求(例如File.ReadAllText),操作系统也会发送异步请求。但是,它不是将控制权交还给您的代码,而是在等待异步请求响应时阻止。这就是适当的异步代码进入的地方 - 而不是等待响应,你给请求一个回调 - 一个在响应回来时执行的函数。

在异步请求期间,没有线程。整个过程发生在一个完全不同的层面 - 比如,请求被发送到NIC上的固件,并给出一个DMA地址来填充响应。当NIC完成您的请求时,它会填充内存,并向处理器发出中断信号。 OS内核通过发信号通知所有者应用程序(通常是IOCP" channel")来完成请求,从而处理中断。这仍然都是在没有任何线程的情况下完成的 - 只是在最后一小段时间内,借用一个线程(在.NET中这是来自IOCP线程池)来执行回调。

所以,想象一个简单的场景。您需要向数据库引擎发送100个同时请求。使用多线程,您可以为每个请求启动一个新线程。这意味着一百个线程,一个hundread线程堆栈,启动一个新线程本身的成本(启动一个新线程很便宜 - 同时启动一百个,而不是那么多),相当多的资源。那些线程只会......阻止。没做什么。当响应到来时,线程被一个接一个地唤醒,并最终被丢弃。

另一方面,使用异步I / O,您可以简单地发布来自单个线程的所有请求 - 并在每个请求完成时注册回调。一百个同步请求将只花费您的原始线程(一旦发布请求就可以免费用于其他工作),以及在请求完成时线程池中的线程短时间 - 最差&#34 34;案例场景,大约与CPU核心一样多的线程。如果您在回调中没有使用阻止代码,当然:)

这并不一定意味着异步代码自动更高效。如果您只需要一个请求,并且在获得响应之前无法执行任何操作,那么使请求异步就没有什么意义。但大多数情况下,这不是您的实际情况 - 例如,您需要在此期间维护GUI,或者您需要同时发出请求,或者您的整个代码都是基于回调的,而不是写入同步(典型的.NET Windows Forms应用程序主要基于事件)。

异步代码的真正好处来自于 - 简化的非阻塞UI代码(不再是"(无响应)"来自窗口管理器的警告),并大大改进了并行性。如果你有一个同时处理一千个请求的Web服务器,你不想浪费1 GiB的地址空间只是为了完全不必要的线程堆栈(特别是在32位系统上) - 你只需要使用线程有事可做。

因此,最后,异步代码使UI和服务器代码更加简单。在某些情况下,主要是使用服务器,它也可以提高效率。效率的提高正是因为在执行异步请求期间没有线程这一事实。

您的评论仅适用于一种特定类型的异步代码 - 多线程并行。在这种情况下,您确实 在执行请求时浪费了一个线程。但是,这并不是人们在说“我的图书馆提供异步API" - 毕竟,这是一个100%毫无价值的API;你可以刚刚调用await Task.Run(TheirAPIMethod)并得到完全相同的东西。