2012-01-12 127 views
6

为什么写下面这段代码被认为是不好的做法,这是什么原因?为什么阻止而不是循环?

while (someList.isEmpty()) { 
    try { 
     Thread.currentThread().sleep(100); 
    } 
    catch (Exception e) {} 
    } 
    // Do something to the list as soon as some thread adds an element to it. 

对我来说,选择一个任意值睡不好的做法,我会在这种情况下使用BlockingQueue,但我想知道如果有一个以上的原因,一个不应该编写这样的代码。

回答

6

它规定的50毫秒的平均延迟作用于事件之前,它唤醒每秒10次时,有没有事件来处理。如果这些事情都不重要,那么这只是不雅。

+0

只是想知道,不是这个等待函数如何在内部工作吗? (显然不包括例外情况。) – Mehrdad 2012-01-12 07:14:52

+1

@ Mehrdad-通常没有。通常这些线程被放置在一个“等待队列”中,而没有给出任何处理器时间。当某些事件发生时会唤醒它们,它们被放回到运行队列中,以便它们被安排。这意味着,如果在任何时候只有两个或三个线程处于活动状态,则可以拥有一百万个睡眠线程,而不会有任何性能损失。 – templatetypedef 2012-01-12 07:16:05

+0

@templatetypedef:那么OS究竟该如何确定线程是否应该在特定的时间片唤醒?它不应该在每个时间片检查循环中线程的状态吗? – Mehrdad 2012-01-12 07:20:52

1

有很多原因不这样做。首先,如您所知,这意味着线程应该响应的事件与实际响应时间之间可能存在较大的延迟,因为线程可能正在休眠。其次,因为任何系统只有很多不同的处理器,所以如果你不得不从处理器中踢出重要线程,以便他们可以告诉线程再次进入休眠状态,那么可以减少系统完成的有用工作总量并增加系统的电力使用(这在系统中很重要,例如电话或嵌入式设备)。

0

还引入竞争条件上您的课。如果您正在使用阻塞队列而不是普通列表 - 线程将阻塞,直到列表中出现新条目。在你的情况下,第二个线程可以在你的工作线程正在休眠时从列表中获得一个元素,而你甚至不会注意到。

0

要添加到其他的答案,你也有,如果你有从队列多个线程删除项目的竞争条件:

  1. 队列为空
  2. 线程A把元素插入队列
  3. 线程B检查队列是否为空;它不是
  4. 线程C检查队列是否为空;它不是
  5. 线程B从队列中取出;成功
  6. 线程C从队列中取出;失败

您可以通过原子地检查,如果队列为空,并且当且仅当没有,取元件从它(内​​块)处理这种;现在你的循环看起来只是一个头发丑陋:

T item; 
while ((item = tryTake(someList)) == null) { 
    try { 
     Thread.currentThread().sleep(100); 
    } 
    catch (InterruptedException e) { 
     // it's almost never a good idea to ignore these; need to handle somehow 
    } 
} 
// Do something with the item 

synchronized private T tryTake(List<? extends T> from) { 
    if (from.isEmpty()) return null; 
    T result = from.remove(0); 
    assert result != null : "list may not contain nulls, which is unfortunate" 
    return result; 
} 

你可以只使用BlockingQueue

+0

难道你不是指'remove(0)'?我假设你不想忽略第一个元素。 – 2012-01-12 08:11:36

+0

erm是,错字,现在修复。 – yshavit 2012-01-12 08:14:08

+0

在你的描述中你引用了一个'queue',但是在你的代码中你使用了'List'。这可能会让人困惑。你可以使用'Queue.remove()';顺便说一句LinkedList也是一个队列。 – 2012-01-12 08:20:32

1

循环是什么不能做一个很好的例子。 ;)


Thread.currentThread().sleep(100); 

没有必要得到currentThread(),因为这是一个静态方法。这是一样的

Thread.sleep(100); 

catch (Exception e) {} 

这是非常不好的做法。如此糟糕,我不会建议你在例子中加入这个,因为有人可能会复制代码。通过打印和阅读给出的例外,可以解决这个论坛上很多问题。


You don't need to busy wait here. esp. when you expect to be waiting for such a long time. Busy waiting can make sense if you expect to be waiting a very very short amount of time. e.g. 

// From AtomicInteger 
public final int getAndSet(int newValue) { 
    for (;;) { 
     int current = get(); 
     if (compareAndSet(current, newValue)) 
      return current; 
    } 
} 

正如你所看到的,它应该是相当罕见的,这种循环需要去各地超过一次,并且指数不太可能去上很多倍。 (在实际应用中,而不是微基准)此循环可能短至10 ns,这不是一个长时间的延迟。


这可能不必要地等待99毫秒。假设制片人在1毫秒后添加了一个条目,它已经等了很长时间。

的解决方案是简单和清晰。

BlockingQueue<E> queue = 

E e = queue.take(); // blocks until an element is ready. 

列表/队列只会在另一个线程以及管理线程和队列更简单的模式改变是使用ExecutorService的

ExecutorService es = 

final E e = 
es.submit(new Runnable() { 
    public void run() { 
     doSomethingWith(e); 
    } 
}); 

正如你所看到的,你不需要直接使用队列或线程。你只需要说出你想让线程池做什么。

0

我不能直接添加到David,templatetypedef等给出的优秀答案 - 如果您想避免线程间通信延迟和资源浪费,请不要使用sleep()循环执行线程间通信。

抢占式调度/调度:

在CPU级,中断是关键。在发生导致其代​​码输入的中断之前,操作系统什么也不做。需要注意的是,在操作系统方面,中断有两种形式 - “真正的”硬件中断,导致要运行的驱动程序和“软件中断” - 这是OS系统从已经运行的线程调用可能会导致该组运行的线程改变。按键,鼠标移动,网卡,磁盘,页面错误都会产生硬件中断。 wait和signal函数和sleep()属于第二类。当硬件中断导致驱动程序运行时,驱动程序会执行设计要执行的任何硬件管理。如果驱动程序需要向操作系统发出某个线程需要运行的信号(可能磁盘缓冲区已满且需要处理),则操作系统会提供一个驱动程序可以调用的入口机制,而不是直接执行中断 - 返回自己,(重要的!)。

中断像上面的例子可以使正在等待就绪线程运行和/或可以使运行进入一个等待状态的线程。在处理完中断代码后,操作系统应用其调度算法/ s来确定在中断之前运行的线程集是否与现在应该运行的集相同。如果是,操作系统只是中断返回,如果没有,操作系统必须抢占一个或多个正在运行的线程。如果操作系统需要抢占正在处理中断的CPU内核上运行的线程,则它必须获得对该内核的控制权。它通过“真正的”硬件中断来实现这一点 - 操作系统内部处理器驱动程序设置了一个硬件信号,用于硬件中断运行要被抢占的线程的内核。

当一个线程是被抢占进入OS代码,操作系统可以保存线程的完整上下文。有些寄存器已经通过中断入口保存到线程堆栈中,因此保存线程的堆栈指针将有效地“保存”所有这些寄存器,但操作系统通常需要做更多工作,例如。可能需要刷新高速缓存,可能需要保存FPU状态,并且在要运行的新线程属于与要被抢先的线程不同的进程的情况下,需要将内存管理保护寄存器换出。通常,操作系统尽快从中断线程堆栈切换到专用操作系统堆栈,以避免在每个线程堆栈上造成操作系统堆栈要求。

一旦保存了上下文,操作系统就可以为要运行的新线程“交换”扩展上下文。现在,操作系统可以最终加载新线程的堆栈指针,并执行中断返回以使其新的就绪线程运行。

然后OS不做任何事情。正在运行的线程一直运行,直到出现另一个中断(硬或软)。

要点:

1)OS内核应该被看作是一个大的中断处理程序,它可以决定中断返回一组不同的线程数少于中断的那些的。

2)操作系统可以控制任何进程中的任何线程,并在必要时停止任何线程,而不管它处于什么状态或可能运行什么内核。

3)抢先调度和分派确实会产生在这些论坛上发布的所有同步等问题。最大的好处是在线程级别对硬中断的快速响应。如果没有这些,那么在PC上运行的所有这些高性能应用程序 - 视频流,快速网络等几乎是不可能的。

4)OS定时器只是一组可以改变运行线程集的中断之一。 '时间切片'(呃 - 我讨厌这个词),就绪线程之间只有当计算机超载时才会发生,即。就绪线程集大于可用于运行它们的CPU内核数。如果任何旨在解释操作系统调度的文本在“中断”之前提到“时间片”,它可能会导致比解释更多的混淆。定时器中断只是“特殊的”,因为很多系统调用都有超时时间来备份它们的主要功能(OK,sleep(),超时是主要功能:)。

相关问题