2012-01-12 131 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(),超時是主要功能:)。

相關問題