当前线程阻塞时,操作系统可以主动切换到另一个线程吗?如果是这样,是不是会使异步编程的价值小得多?

Posted

技术标签:

【中文标题】当前线程阻塞时,操作系统可以主动切换到另一个线程吗?如果是这样,是不是会使异步编程的价值小得多?【英文标题】:Can OS actively switch to another thread when the current thread is blocking? If so, does it make the value of asynchronous programming much smaller?当前线程阻塞时,操作系统可以主动切换到另一个线程吗?如果是这样,是否会使异步编程的价值小得多? 【发布时间】:2021-11-23 23:17:38 【问题描述】:

好的,让我试着重述两个问题:

    操作系统是否会在线程开始阻塞时主动抢占线程,并且在阻塞完成之前永远不会返回线程?我觉得操作系统有关于磁盘 IO 和网络 IO 的信息,所以它应该有足够的信息来这样做。 如果操作系统可以通过切换到另一个线程来消除 CPU 空闲时间,我们真的需要异步编程吗?

【问题讨论】:

所以在你的理论中,如果一个线程没有等待 IO 或网络,它可以停止整个计算机,因为永远没有理由从它切换。请参阅what actually happens。另请参阅what used to happen。你所描述的不会发生。 @GSerg 我从来没有说过如果线程没有阻塞就不会被抢占。 这是您问题的必要前提,等待IO是抢占的触发器。如果你知道操作系统会抢占一个线程,不管它是否在等待 IO,那么操作系统就没有理由去判断线程是否在等待,不是吗?无论如何它都会被抢占。就像在做if (condition()) then x = 1 else x = 1,可以简化为x = 1 @GSerg 我知道无论线程是否阻塞,操作系统都可以抢占线程。但是如果抢占策略不考虑阻塞,那么仍然会有 CPU 阻塞的时候(如果操作系统在线程开始阻塞时没有抢占线程)。如果线程一开始阻塞,操作系统就主动抢占线程,可以进一步减少CPU阻塞时间。我在帖子中要问的是操作系统是否能够这样做。 所以基本上你是在问一些操作系统中的线程调度程序如何处理处于等待状态的线程?没有“the”线程调度程序,因此每个操作系统的答案都会有所不同;对于 Windows,请参阅 microsoftpressstore.com/articles/…,其中讨论了进入和退出等待状态的转换。 【参考方案1】:

因此,即使线程阻塞,CPU 也不会阻塞 但运行其他线程。

是的,没错。

如果我上面的理解是正确的,那么 异步编程?

如果您希望您的程序同时在多个任务上取得进展,这很有用。

当然,您可以通过自己显式生成多个线程并让每个线程处理单独的任务(包括任何阻塞调用)来获得相同的效果,这也可以,但是您必须显式管理所有线程那些可能有点痛苦的线程。 (线程间同步/通信可能会很棘手,尤其是在您想要取消操作的情况下,如果您的一个或多个线程在阻塞 I/O 调用中被阻塞,因此无法很好地实现很容易被说服快速退出——那么你的其他线程可能需要等待很长时间,可能永远等待,然后才能join()该线程并安全终止)

【讨论】:

异步编程不是管理线程的方法。请参阅***.com/a/34681101/11683。 当然可以,但是如果您无法访问异步编程 API,则可以使用线程来实现。【参考方案2】:

迄今为止,异步编程的复兴与操作系统/内核架构几乎没有关系。自 1960 年代以来,操作系统已经阻止了您描述的方式;虽然原因有所改变。

在早期系统中,效率是通过有用的 CPU 周期来衡量的;因此,当一个人被阻止时切换任务是一种自然的行为。

现代系统架构经常解决如何保持 CPU 占用;例如,如果有 800 个 CPU 但有 20 个任务;那么 780 CPU 就没事了。

作为一个具体的例子,一个对文件的所有字节求和的程序可能看起来有点像:

for (count=0;  (c = getchar()) != EOF; count += c) 

为了提高性能,多线程版本可能如下所示:

for (n=0; n < NCPU; n++) 
    if (threadfork() == 0) 
        offset_t o = n* (file_size / NCPU);
        offset_t l = (file_size / NCPU);
        for (count = 0; l-- && pread(fd, &c, 1, o) == 1; count += c) 
        threadexit(count);
    

for (n=0; n < NCPU; n++) 
    threadwait(&temp);
    total += temp;

return total;

这有点可怕,因为它很复杂,而且可能有不一致的加速。 比较函数:

int GetSum(char *data, int len) 
    int count = 0;
    while (len--) 
        count += *data++;
    
    return count;

我可以构造一种调度程序,当内存中的文件数据块可用时,它会在其上调用 GetSum(),将其返回值排队以供以后累积。该调度程序可以投资于熟悉最佳 i/o 模式等。因为它可能适用于许多问题;程序员的工作要简单得多。

即使没有那种原生支持;我可以mmap(2) 文件,然后调度许多线程来触摸一个页面,然后在该页面上调用GetSum。这将有效地在普通的旧 unix-y 框架中模拟异步模型。

当然,没有什么是那么容易的;即使是面向调度的异步模型中的进度条充其量也是值得怀疑的(并不是说基于 1950 年代的顺序模型有什么可写的)。传达错误也很麻烦;并且因为您使用 asynch 将最大的 cpu 资源分配给自己,所以您需要最小化同步操作(duh,async :)。

异步有很多可能性;但它确实需要具有事实上的异步支持的语言,而不是对某些摇摇欲坠的语言的最新 du jour 标准 抱有抱负的点头。

【讨论】:

以上是关于当前线程阻塞时,操作系统可以主动切换到另一个线程吗?如果是这样,是不是会使异步编程的价值小得多?的主要内容,如果未能解决你的问题,请参考以下文章

java线程调度

进程切换 多线程并发

非阻塞的Win32系统调用(例如ReleaseMutex)会导致线程阻塞并允许较低优先级的线程运行吗?

java线程调度

29.Windows线程切换之被动切换(KiDispatchInterrupt)

解决线程切换导致ThreadLocal值丢失