据我所知,使用busy-wait不是一个好的编程习惯,最好尽可能使用同步对象(wait-notify)。但是我想知道一个人是否准备好牺牲cpu周期,然后忙着等待更快或等待通知?
我假设wait-notify将涉及对同步对象的内部锁定,并且信号可能来自内核以唤醒线程,使得这种方法比忙等待更慢,其中人们可以连续检查条件直到满意为止。只要满足此条件(例如,布尔值== true),线程就可以从繁忙的等待中退出。根据我的理解,我感到很忙 - 等待应该更快。
如果我的论点是错误的,如果其他人可以分享他们的想法并纠正我,我将不胜感激。
答案 0 :(得分:8)
实验表明,如果你等待并等待并通知(在我的硬件上,无论如何),你会更快地看到旗帜。 (详情如下。)差异是 非常非常非常小 ,所以这只适用于非常罕见的应用。例如,股票交易应用程序,公司可以获得他们可以获得的任何优势(争取尽可能在交易所附近找到他们的服务器,以便从交易所获得微网改进等)可能会认为差异是值得的。我也可以想象一些科学应用。
在绝大多数应用中,差异实际上没有任何区别。
但是,CPU发生的事情当然是其中一个核心硬件:
在影响盒子上的其他进程和数据中心的功耗方面,这是不好的。
所以:极度不情愿地使用,只有在真正重要的情况下使用。
数据(非常小的样本,但代码如下):
Busy Wait: 10631 12350 15278 Wait and Notify: 87299 120964 107204 Delta: 76668 108614 91926
时间是 nano 秒。十亿分之一秒。上面的平均增量为92403ns(0.092402667毫秒,0.000092403秒)。
BusyWait.java
:
public class BusyWait {
private static class Shared {
public long setAt;
public long seenAt;
public volatile boolean flag = false;
}
public static void main(String[] args) {
final Shared shared = new Shared();
Thread notifier = new Thread(new Runnable() {
public void run() {
System.out.println("Running");
try {
Thread.sleep(500);
System.out.println("Setting flag");
shared.setAt = System.nanoTime();
shared.flag = true;
}
catch (Exception e) {
}
}
});
notifier.start();
while (!shared.flag) {
}
shared.seenAt = System.nanoTime();
System.out.println("Delay between set and seen: " + (shared.seenAt - shared.setAt));
}
}
WaitAndNotify.java
:
public class WaitAndNotify {
private static class Shared {
public long setAt;
public long seenAt;
public boolean flag = false;
}
public static void main(String[] args) {
(new WaitAndNotify()).test();
}
private void test() {
final Shared shared = new Shared();
final WaitAndNotify instance = this;
Thread notifier = new Thread(new Runnable() {
public void run() {
System.out.println("Running");
try {
Thread.sleep(500);
System.out.println("Setting flag");
shared.setAt = System.nanoTime();
shared.flag = true;
synchronized (instance) {
instance.notify();
}
}
catch (Exception e) {
}
}
});
notifier.start();
while (!shared.flag) {
try {
synchronized (this) {
wait();
}
}
catch (InterruptedException ie) {
}
}
shared.seenAt = System.nanoTime();
System.out.println("Delay between set and seen: " + (shared.seenAt - shared.setAt));
}
}
答案 1 :(得分:2)
一个人准备好在繁忙的等待中牺牲CPU周期,因为它更快。 忙碌等待是实时低延迟应用的示例。
有一个名为lmax disruptor的框架是为伦敦证券交易所建立的,其中一个锁定策略是忙等待,这就是他们使用它的方式。
为了达到超快速度,在通知锁定的情况下,最好浪费cpu周期,确保等待时间。
你对所有其他的东西都是正确的,如果你稍微谈论破坏者并阅读他们的论文,你会得到更多的澄清。关于高性能和低延迟,有太多事情要说。
要看的一个好博客是Mechanical Sympathy。
答案 2 :(得分:0)
忙碌等待比正常等待通知更快。
但你为什么要等?因为一个生产者或其他线程会做一些工作,然后设置一个条件(或通知),以便你实际上可以退出忙/等待循环。 现在假设你的制作人正在做一些繁重的任务,那么你实际上是通过忙碌等待(主要是在单处理器系统中)来吃掉它的cpu周期,这反过来可能会使你的系统整体变慢。
所以现在你应该使用忙等待。 正如Claudio所说,它主要用于低延迟系统。 但仍然不能盲目使用它。当你的制片人使用忙等待 正以稳定的速度生产。 如果您的制作人以可变利率生成项目(通常由泊松分布证明),那么您应该使用等待通知。
通常情况下,高吞吐量和低延迟系统的最佳折衷方案是使用 做一段忙碌的等待,然后去等待()。 如果您的系统需要超低持续性,那么您可以进行许多优化 其中一个可能是Busy-Wait。 但它不应该是每个线程都忙着等待。确保只有一些消费者 可能在N / 2左右消费者正忙着等待N是您的核心数量 系统。浪费CPU周期可能会影响系统的整体性能和响应能力。 对于您的参考:Even Normal ReentrantLock及其变体适用于这些策略。 I,E 即当线程调用lock.lock()时,它会尝试在进入队列之前获取锁定两次并等待锁定被释放。对于低延迟系统,您甚至可以为进入队列之前尝试10次以上的特定场景定义自己的锁(它们将是所谓的自旋锁的变体)
答案 3 :(得分:0)
取决于。有几种情况:
情景A
如果在'busy-wait'中等待的是硬件操作(例如,从硬盘读取扇区到内存):
1)硬件将执行操作。
2)驱动程序将启动中断。
3)操作将暂停实际过程(忙碌等待过程),保存任何CPU注册中断将在其处理中覆盖的实际值。
4)中断将被处理,修改任何指示数据可用的标志(如果是磁盘读取)。
5)将恢复任何覆盖寄存器。
6)您的流程将继续其流程。只是在下一次迭代中,它将调用循环的conthe条件。例如,如果忙等待是:
while( !fileReady() ){
...
}
fileReady()方法将是一个内部检查是否设置了具体标志(在4中修改的标志)的方法。 7)所以在下一次迭代中,循环将进入并执行操作。
请记住,如果有另一个进程正在运行(操作系统进程,其他程序),它们会将您的进程置于进程尾。此外,操作系统可以决定,鉴于您的进程已经使用了他可以使用的所有CPU周期(它花费了它的量子),它将具有较少的优先级,其他进程进入休眠状态(而不是使用忙等待方法)需要等待一定的条件。
结论。如果没有其他外部进程在同一个内核/ CPU中运行(非常不可能),它会更快。
情景B
另一方面,如果忙方法正在等待另一个进程结束(或将任何变量设置为某个值,则忙等待将会更慢。
1)busy方法将在CPU中运行。由于其他进程没有运行,因此条件不能更改,因此busy方法将一直运行,直到CPU决定为另一个进程提供CPU时间。
2)另一个进程将运行。如果这个过程花费时间而没有实现繁忙等待过程需要的混乱,那么转到1),否则继续3)
3)另一个(不是忙等待)进程仍会运行一段时间,直到cpu决定新的进程更改。
4)busy-method将再次运行,但现在条件已满足,因此现在已完成操作。
结论:它较慢,我们也会减慢整个过程。
情景C
如果我们的方案与B相同,但有多个核心(每个核心中有一个进程),该怎么办?
首先,请记住,即使您有一个具有多个内核的CPU,您的程序也可能不允许使用多个内核。也许有一些操作系统或其他程序使用它们。
其次,它不值这个价格。请记住,该过程必须通信以允许忙碌等待发现条件满足。这通常可以通过'final'变量来完成,因此每次评估条件时都需要在同步块中输入(在进入循环之前无法锁定并且没有解锁它,因为在其他过程中)将无法更改变量。所以你需要这样的东西:
boolean exit = false;
while( exit==false ){
synchronize(var){
if(var = CONDITIONS_MEET)
exit=true;
}
}
//operations...
}
然而,wait-notify会做类似的事情,并且更有效率(在语言级别),而不会浪费CPU周期,并使用良好的prectices !!
结论:你的生活变得复杂,不太可能会更快(非常非常不可能)。
最终结论:只有当您了解操作系统的具体细节以及运行程序的环境时,才会处于一个非常简单的场景中,您可以考虑忙等待的方法
我希望这会解决你的问题。不要犹豫,问一下是不是很清楚。