簡體   English   中英

虛假喚醒是否會解除所有等待線程的阻塞,甚至是不相關的線程?

[英]Does a spurious wake up unblock all waiting threads, even the unrelated ones?

我還是 C++ 多線程的新手,我目前正試圖圍繞“虛假喚醒”以及導致它們的原因。 我已經對條件變量、內核信號、futex 等進行了一些挖掘,並發現了幾個關於“虛假喚醒”發生的原因和方式的罪魁禍首,但仍有一些我無法找到答案的問題。 .

問題:虛假喚醒會解除所有等待/阻塞線程的阻塞,甚至是等待完全無關通知的線程嗎? 或者被阻塞的線程是否有單獨的等待隊列,因此等待另一個通知的線程受到保護?

示例:假設我們有 249 名斯巴達人等待進攻波斯人。 他們wait()他們的領袖Leonidas(第250 位)通知notify_all()何時發起攻擊。 現在,在營地的另一邊,有 49 名受傷的斯巴達人正在等待醫生(第 50 名)通知notify_one()以便他可以治療每個人。 虛假的喚醒會解除所有等待的斯巴達人,包括受傷的人,還是只會影響等待戰斗的人? 等待線程有兩個單獨的隊列,還是只有一個隊列?

如果該示例具有誤導性,請道歉......我不知道如何解釋它。

虛假喚醒的原因因每個操作系統而異,此類喚醒的屬性也是如此。 例如,在 Linux 中,當一個信號被傳遞到一個被阻塞的線程時就會發生喚醒。 執行信號處理程序后,線程不會再次阻塞,而是從它被阻塞的系統調用接收一個特殊的錯誤代碼(通常是EINTR )。 由於信號處理不涉及其他線程,因此它們不會被喚醒。

請注意,虛假喚醒不取決於您阻塞的同步原語或該原語上阻塞的線程數。 它也可能發生在非同步阻塞系統調用中,如readwrite 通常,您必須假設任何阻塞系統調用都可能因任何原因過早返回,除非像 POSIX 這樣的規范保證它不會返回(即使如此,也可能存在偏離規范的錯誤和操作系統細節)。

有些人將多余的通知歸因於虛假喚醒,因為處理兩者通常是相同的。 但是,它們並不相同。 與虛假喚醒不同,多余的通知實際上是由另一個線程引起的,是對條件變量或 futex 進行通知操作的結果。 這只是您檢查喚醒的條件可能會在未阻塞的線程設法檢查它之前變為 false。

在條件變量的上下文中,虛假喚醒只是從服務員的角度來看。 表示等待退出,但條件不成立; 因此慣用的用法是:

Thing.lock()
 while Thing.state != Play {
     Thing.wait()
 }
 ....
 Thing.unlock()

這個循環的每一次迭代都被認為是虛假的。 它們出現的原因:

  1. 許多條件被多路復用到一個條件變量上; 有時這是合適的,有時只是懶惰
  2. 一個等待的線程擊敗了你的線程,並在你有機會擁有它之前改變了它的狀態。
  3. 不相關的事件,例如 kill(2) 處理,這樣做是為了確保異步處理程序運行后的一致性。

最重要的是驗證是否滿足了所需的條件,如果不滿足則重試或放棄。 重新檢查可能很難診斷的情況是一個常見的錯誤。

作為一個更嚴重的例子應該說明:

int q_next(Q *q, int idx) {
/* return the q index succeeding this, with wrap */
   if (idx + 1 == q->len) {
       return 0
   } else {
       return idx + 1
   }
}
void q_get(Q *q, Item *p) {
    Lock(q)
    while (q->head == q->tail) {
         Wait(q)
    }
    *p = q->data[q->tail]

    if (q_next(q, q->head) == q->tail) {
        /* q was full, now has space */
        Broadcast(q)
    }
    q->tail = q_next(q, q->tail)
    Unlock(q)
}
void q_put(Q *q, Item *p) {
    Lock(q)
    while (q_next(q, q->head) == q->tail) {
         Wait(q)
    }
    q->data[q->head] = *p
    if (q->head == q->tail) {
        /* q was empty, data available */
        Broadcast(q)
    }
    q->head = q_next(q, q->head)
    Unlock(q)
}

這是一個多讀多寫隊列。 寫入者等到隊列中有空間時,將項目放入,如果隊列之前為空,則廣播以指示現在有數據。 讀者等到隊列中有東西時,從隊列中取出項目,如果隊列之前已滿,則廣播以指示現在有空間。

請注意,條件變量用於兩個條件 {not full, not empty}。 這些是邊沿觸發條件:只有從滿和從空的轉換才會發出信號。

Q_get 和 q_put 保護自己免受由 [1] 和 [2] 引起的虛假喚醒,您可以輕松地檢測代碼以顯示這種情況發生的頻率。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM