什么是多线程环境中的繁忙旋转?

时间:2014-07-31 19:10:24

标签: java multithreading

多线程环境中的“忙碌旋转”是什么?

它是如何有用的,如何在多线程环境中用java实现它?

它在提高应用程序性能方面有何用处?

7 个答案:

答案 0 :(得分:16)

其他一些答案在忙碌的等待中错过了真正的问题。

除非你在谈论一个关注保存电子电源的应用程序,否则燃烧CPU时间本身并不是一件坏事。当有一些其他线程或进程可以运行时,它才会变坏。当其中一个准备运行的线程是忙等待循环等待的线程时,真的坏了。

这是真正的问题。在正常操作系统上运行的普通用户模式程序无法控制哪些线程在哪些处理器上运行,正常操作系统无法区分繁忙等待的线程和正在工作的线程之间的区别,即使操作系统知道线程正忙着等待,它也无法知道线程在等待什么。

因此,忙碌的服务员完全有可能等待很多毫秒(几乎是永恒的),等待一个事件,而唯一可以使事件发生的线程就在旁边(即,在运行队列中)等待轮到使用CPU。

繁忙等待通常用于可以严格控制哪些线程在哪些处理器上运行的系统中。当您知道导致它的线程实际上在不同的处理器上运行时,忙等待是等待事件的最有效方式。当您为操作系统本身编写代码时,或者当您编写在实时操作系统下运行的嵌入式实时应用程序时,通常会出现这种情况。


凯文沃尔特斯写了关于等待时间非常短的案例。可以允许在普通OS上运行的CPU绑定的普通程序在每个时间片中执行数百万条指令。因此,如果程序使用自旋锁来保护仅由几条指令组成的关键部分,那么任何线程在关键部分中都不会丢失其时间片。这意味着,如果线程A发现自旋锁被锁定,那么持有锁的线程B很可能实际上 在不同的CPU上运行。这就是为什么当你知道它将在多处理器主机上运行时,在普通程序中使用自旋锁是可以的。

答案 1 :(得分:9)

繁忙等待或旋转是一种技术,在这种技术中,进程会反复检查条件是否为真,而不是调用wait或sleep方法并释放CPU。

1.它主要用于多核处理器,其中条件很快就会成立,即以毫秒或微秒为单位

2.不释放CPU的优点是,所有缓存的数据和指令都不受影响,如果此线程暂停在一个核心上并带回另一个线程,则可能会丢失所有缓存的数据和指令

答案 2 :(得分:5)

忙碌旋转是 等待事件而不释放CPU 的技巧之一。通常这样做是为了避免丢失CPU缓存中的数据,如果线程暂停并在其他核心中恢复,则会丢失数据。

因此,如果您正在处理订单处理线程目前没有任何订单的低延迟系统,而不是休眠或调用wait(),您可以循环然后再次检查队列新消息。如果你需要等待很短的时间,这是唯一有益的,例如微秒或纳秒。

LMAX Disrupter框架,一个高性能的线程间消息传递库,它有一个BusySpinWaitStrategy,它基于这个概念,并为等待屏障的EventProcessors使用繁忙的自旋循环。

答案 3 :(得分:3)

A"忙碌的旋转"在一个线程中不断循环以查看其他线程是否已完成某些工作。这是一个不好的想法"因为它只是在等待消耗资源。最忙碌的旋转甚至没有睡觉,但是尽可能快地旋转等待工作完成。通过直接完成工作来通知等待线程并让它在此之前保持睡眠状态就不那么浪费了。

注意,我称之为“坏主意”#34;但在某些情况下,它会用于低级代码以最大程度地减少延迟,但这在Java代码中很少(如果有的话)。

答案 4 :(得分:3)

从性能的角度来看,忙碌的旋转/等待通常是一个坏主意。在大多数情况下,最好在准备好运行时睡觉并等待信号,而不是旋转。采取有两个线程的场景,线程1正在等待线程2设置一个变量(比如,它等到var == true。然后,它会忙着旋转

while (var == false)
    ;

在这种情况下,您将占用线程2可能正在运行的大量时间,因为当您醒来时,您只是无意识地执行循环。所以,在你等待这样的事情发生的情况下,最好让线程2完全控制,让自己进入睡眠状态并让它在完成时唤醒你。

但是,在极少数情况下,您需要等待的时间非常短,实际上更快的自旋锁。这是因为执行signalng功能所需的时间;如果使用的旋转时间小于执行信号所需的时间,则旋转是优选的。因此,以这种方式它可能是有益的,并且实际上可以提高性能,但这绝对不是最常见的情况。

答案 5 :(得分:3)

旋转等待是你经常等待条件成真。相反的是等待信号(如notify()和wait()的线程中断)。

有两种等待方式,第一种是半活动(睡眠/产量)和活动(忙碌等待)。

在忙碌的等待中,程序使用HLT或NOP等特殊操作代码或其他耗时的操作主动闲置。其他只使用while循环检查条件是否为真。

JavaFramework为线程提供Thread.sleep,Thread.yield和LockSupport.parkXXX()方法来移交cpu。睡眠等待特定的时间,但即使指定了纳秒,alwasy也会花费一毫秒。 LockSupport.parkNanos(1)也是如此。 Thread.yield允许我的示例系统(win7 + i5 mobile)的分辨率为100ns。

产量问题是它的工作方式。如果系统使用完全,在我的测试场景中,产量最多可能需要800毫秒(100个工作线程都会无数次地计算一个数字(a + = a;))。由于yield释放了cpu并将线程添加到其优先级组中所有线程的末尾,因此yield不稳定,除非cpu在某个范围内没有被利用。

忙等待会阻塞CPU(核心)达数毫秒。

Java Framework(检查条件类实现)使用活动(忙)等待小于1000ns(1微秒)的周期。在我的系统中,平均调用System.nanoTime需要160ns,所以忙碌的等待就像检查条件在nanoTime上花费160ns并重复一样。

所以基本上Java(队列等)的并发框架就像在微秒旋转下等待并在N个粒度内点击等待的周期,其中N是用于检查时间约束并等待一毫秒或更长时间的纳秒数(对于我目前的系统。)

如此积极的忙碌等待会提高利用率,但有助于系统的整体反应性。

在烧毁CPU时间时,应该使用特殊指令来降低执行耗时操作的内核的功耗。

答案 6 :(得分:0)

繁忙的旋转只不过是循环,直到线程完成。例如。你说10个线程,你想等待所有线程完成然后想继续,

while(ALL_THREADS_ARE_NOT_COMPLETE);
//Continue with rest of the logic

例如,在java中,您可以使用ExecutorService

管理多个线程
    ExecutorService executor = Executors.newFixedThreadPool(10);
    for (int i = 0; i < 10; i++) {
        Runnable worker = new WorkerThread('' + i);
        executor.execute(worker);
    }
    executor.shutdown();
    //With this loop, you are looping over till threads doesn't finish.
    while (!executor.isTerminated());

这是一个繁忙的旋转,因为它消耗资源,因为CPU不是理想的,但保持在循环上运行。我们应该有机制来通知主线程 (父线程)表示所有线程都已完成,并且可以继续执行剩余的任务。

使用前面的示例,您可以使用不同的机制来提高性能,而不是忙碌旋转。

   ExecutorService executor = Executors.newFixedThreadPool(10);
    for (int i = 0; i < 10; i++) {
        Runnable worker = new WorkerThread('' + i);
        executor.execute(worker);
    }
    executor.shutdown();
    try {
           executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
    } catch (InterruptedException e) {
        log.fatal("Exception ",e);
    }