同步执行异步操作会阻塞CPU吗?

4
当一个线程同步执行一个本可以被等待的异步方法(例如,一个耗时的网络操作),该线程将无法被重用。但是,这个线程是否会在空闲时浪费CPU资源呢?或者.NET / 操作系统是否知道一个线程当前正在主动等待,并且不会将其分配给CPU?
string result = DownloadStringAsync().Result // will I be wasting CPU resources as well or just blocking the current thread?

这是不言而喻的,但请不要写出这样的代码。

1
“闲跑”是一个自相矛盾的词。如果一个线程是空闲的,那它就不在运行。那段代码将会阻塞一个线程,但该线程并不会占用CPU时间。 - canton7
2个回答

7
不,阻塞并不会浪费CPU时间,至少不会浪费大量的时间。它确实会“浪费”一些内存,因为每个线程都有一些内存,在被阻塞期间该线程无法用于其他任何操作。
阻塞一个线程会将其标记为不可执行状态,调度器会选择其他线程来执行,直到被阻塞的线程解除阻塞为止。
还有一个概念叫做“自旋锁”,它本质上只是一个循环检查某个共享变量。这种方式确实会浪费CPU时间,但可以降低延迟,因为线程不会失去其时间片。然而,如果持有锁的线程由于等待线程独占CPU而无法被调度运行,可能会导致问题。
使用“Monitor”(监视器)的同步机制,如“lock”关键字,采用了混合方案。它首先使用自旋锁进行一定数量的循环,如果在此期间监视器仍不可用,则暂停线程。我不确定“.Result”是否使用监视器或其他同步机制,但无论哪种情况,您都不必担心这个问题。

如果你编写一个 UI 程序,你应该担心死锁问题。因为在 UI 线程上使用 .Result 来等待需要完成的任务会导致死锁。而且有时候很难确定一个任务是否需要 UI 线程来完成。

另一个需要关注的潜在问题是线程池耗尽,即所有或大部分线程都被阻塞,这样可运行的任务无法获取线程来执行。线程池会创建新线程以避免完全锁定,但这是有意为之的慢操作。


5
当一个线程同步执行异步方法时...

请允许我纠正您的术语。异步方法通常不在线程上运行。它们大部分使用的是与CPU不同的硬件,如网络卡或磁盘驱动程序,其中"线程"的概念并不适用。当您等待一个Task时,无论是使用.Wait().Result还是.GetAwaiter().GetResult(),您都会指示当前线程被阻塞,直到任务完成。线程的阻塞对异步操作没有任何帮助。它只是异步操作附带的事件,您出于自己的原因决定要附加它。内部发生的事情(源代码)是实例化了一个ManualResetEventSlim,它被连接到任务的继续执行信号,并调用其方法WaitManualResetEventSlim.Wait方法会阻塞当前线程,直到另一个线程将组件Set为止。被阻塞的线程不消耗CPU资源

Workers and slackers


线程阻塞不会消耗CPU资源 - 仅当阻塞由阻止原语引起时。基于自旋的原语会消耗资源,尽管我所知的是ManualResetEventSlim使用了混合方法。 - Ryan
一个被阻塞的线程不会消耗CPU资源,只有在由阻塞原语引起的阻塞时才是如此。基于自旋的原语会消耗资源。不过据我所知,ManualResetEventSlim采用了混合方法。 - undefined
@Ryan 当然,有些原语在让线程休眠之前会进行一些旋转操作,但旋转的持续时间非常短(纳秒级别),几乎可以忽略不计。我认为OP并不担心这个问题。 - Theodor Zoulias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接