【问题标题】:Is it Really Busy Waiting If I Thread.Sleep()?如果我使用 Thread.Sleep() 真的很忙吗?
【发布时间】:2014-08-29 02:49:50
【问题描述】:

我的问题对定义有点挑剔:

下面的代码可以描述为“忙着等待”吗?尽管它使用 Thread.Sleep() 来允许上下文切换?

while (true) {
    if (work_is_ready){
        doWork();
    }
    Thread.Sleep(A_FEW_MILLISECONDS);
}

PS - Wikipedia 中当前对忙等待的定义表明它是一种“不那么浪费”的忙等待形式。

【问题讨论】:

    标签: c# multithreading thread-sleep


    【解决方案1】:

    任何轮询循环,无论轮询操作之间的时间如何,都是忙碌的等待。诚然,睡眠几毫秒比完全不睡眠要“忙”得多,但它仍然涉及处理:线程上下文切换和一些最小的条件检查。

    非忙等待是阻塞调用。您的示例的非繁忙版本将涉及等待同步原语,例如事件或条件变量。比如这个伪代码:

    // initialize an event to be set when work is ready
    Event word_is_ready;
    work_is_ready.Reset();
    
    // in code that processes work items
    while (true)
    {
        work_is_ready.Wait();  // non-busy wait for work item
        do_work();
    }
    

    这里的区别是没有定期轮询。 Wait 调用阻塞,并且在设置事件之前永远不会调度线程。

    【讨论】:

    • c2.com(好像是权威软件设计网站)不同意你的看法。以下是他们的示例:BusyWaiting: while ( MyFileIsNotReady() ) { //Do nothing } 和轮询:while ( MyFileIsNotReady() ) { Sleep(1000); }。我知道睡眠是唯一的区别。
    【解决方案2】:

    这不是忙着等待。忙碌的等待或旋转涉及相反的情况:避免上下文切换。

    如果要允许其他线程运行,当且仅当其他线程准备好运行时,以避免单线程 CPU 中的死锁情况(例如,当前线程需要将 work_is_ready 设置为 true,但如果该线程不放弃处理器让其他人运行,永远不会设置为true),可以使用Thread.Sleep(0)

    很多更好的选择是使用SpinWait.SpinUntil

    SpinWait.SpinUntil(() => work_is_ready);
    doWork();
    

    SpinWait 发出一个特殊的rep; nop(重复无操作)或pause 指令,让处理器知道您正忙于等待,并针对超线程 CPU 进行了优化。 此外,在单核 CPU 中,这将立即yield 处理器(因为如果只有一个核,忙等待是完全没用的)。


    但旋转只有在你绝对确定你不会等待一个条件的时间超过处理器将上下文切换出并再次切换回所花费的时间时才有用。即,不超过几微秒。

    如果您想每隔几毫秒轮询一次条件,那么您应该使用阻塞同步原语,正如 wiki 页面所建议的那样。对于您的场景,我建议使用 AutoResetEvent,它会在调用 WaitOne 时阻塞线程,直到事件发出信号(即条件变为真)。

    另请阅读:Overview of Synchronization Primitives

    【讨论】:

    • 我不认为在“几毫秒”的时间内旋转会更有效率。上下文切换占用核心大约 30 微秒;旋转会在整个等待时间(3mS == 30,000 微秒)期间浪费所有内核的 CPU 周期。旋转只有在旋转所花费的时间少于进行上下文切换所需的时间时才有意义。
    • @JeremyFriesner 你说得对,我没有意识到 OP 意味着要等那么久。我会尽快更新我的答案。
    【解决方案3】:

    这取决于操作系统和您睡眠的确切毫秒数。如果睡眠时间足够长,操作系统可以切换到另一个任务,填充其缓存,并有效地运行该任务,直到您的任务准备好再次运行,那么它就不会忙于等待。如果不是,那就是。

    要批评这段代码,我会这样说:“如果睡眠时间太小,无法让内核在检查之间做有用的工作,这段代码可能会忙于等待。应该改变它,以便编写这段代码的代码需要做的工作会触发该响应。”

    这种糟糕的设计造成了一个不必要的设计问题——睡眠时间应该多长?如果它太短,你忙着等待。如果时间太长,工作就会被搁置。即使它足够长以至于您不忙于等待,您也会强制进行不必要的上下文切换。

    【讨论】:

      【解决方案4】:

      当您的代码处于休眠状态时,从技术上讲,它将处于休眠状态以释放 CPU。在忙于等待时,您的代码会占用 CPU 直到满足条件。

      下面的代码可以描述为“忙着等待”吗?尽管它使用 Thread.Sleep() 来允许上下文切换?

      不是忙等待,而是轮询比忙等待更高效。两者是有区别的

      简单地说,忙等待是阻塞的,而轮询是非阻塞的。

      【讨论】:

        【解决方案5】:

        忙碌的等待是这样的:

        for(;;) {
          if (condition) {
              break;
          }
        }
        

        条件可以是“检查当前时间”(例如性能计数器轮询)。有了这个,您可以在线程中获得非常准确的暂停。这对于例如低级 I/O(切换 GPIO 等)很有用。因此,您的线程一直在运行,并且如果您使用协作多线程,则您完全可以控制线程将等待您多长时间。通常这种线程具有高优先级并且是不可中断的。

        现在一个不忙的等待意味着,线程是不忙的。它允许另一个线程执行,所以有一个上下文切换。要允许上下文切换,在大多数语言和操作系统中,您可以简单地使用 sleep()。还有其他类似的函数,如 yield()、wait()、select() 等。它取决于操作系统和语言,如果它们是非忙或忙实现。但根据我的经验,在所有情况下,睡眠 > 0 总是不忙。

        非忙等待的优点是允许其他线程运行,其中包括空闲线程。有了这个,您的 CPU 可以进入省电模式、时钟下降等。它还可以运行其他任务。在指定时间之后,调度程序会尝试返回您的线程。但这只是一种尝试。这并不准确,可能比您的睡眠定义的时间长一点。

        我想。现在很清楚了。

        现在最大的问题是:这是忙还是非忙等待:

        for(;;) {
          if (condition) {
              break;
          }
          sleep(1);
        }
        

        答案是:是非忙等待。 sleep(1) 允许线程执行上下文切换。

        现在下一个问题:第二个 for() 是忙,还是非忙等待:

        function wait() {
          for(;;) {
            if (condition) {
                break;
            }
          }
        }
        
        for(;;) {
          wait();
          if (condition) {
            break;
          }
          sleep(1);
        }
        

        很难说。这取决于 wait() 函数的实际执行时间。如果它什么都不做,那么 CPU 几乎整个时间都在 sleep(1) 中。这将是一个非阻塞的 for 循环。但是如果wait()是一个繁重的计算函数,不允许线程上下文切换,那么这整个for循环可能会变成一个阻塞函数,即使有sleep(1)。想想最坏的情况:wait() 函数永远不会返回给调用者,因为很长一段时间都没有满足条件。

        这里很难回答,因为我们不知道条件。您可以通过以下方式想象问题,您无法回答问题,因为您不知道条件:

        if (unkonwnCondition) {
          for(;;) {
            if (condition) {
                break;
            }
          }
        } else {
          for(;;) {
            if (condition) {
                break;
            }
            sleep(1);
          }
        }
        

        如你所见,它是一样的:因为你不知道条件,你不能说等待是忙还是不忙。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-01-25
          • 2011-08-25
          • 2021-05-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-04
          • 1970-01-01
          相关资源
          最近更新 更多