pthread_cond_timedwait - pthread_cond_wait



為什麼pthreads的條件變量函數需要互斥鎖? (6)

POSIX條件變量是無狀態的。 所以維護國家是你的責任。 由於狀態將被等待的線程以及告訴其他線程停止等待的線程訪問,它必須受互斥體保護。 如果你認為你可以使用不帶互斥量的條件變量,那麼你還沒有意識到條件變量是無狀態的。

條件變量是建立在條件的基礎上的。 等待條件變量的線程正在等待一些條件。 表示條件變量的線程會改變​​該條件。 例如,一個線程可能正在等待一些數據到達。 其他一些線程可能會注意到數據已經到達。 “數據已到”是條件。

以下是條件變量的簡單使用:

while(1)
{
    pthread_mutex_lock(&work_mutex);

    while (work_queue_empty())       // wait for work
       pthread_cond_wait(&work_cv, &work_mutex);

    work = get_work_from_queue();    // get work

    pthread_mutex_unlock(&work_mutex);

    do_work(work);                   // do that work
}

看看線程是如何等待工作的。 這項工作受到互斥體的保護。 等待釋放互斥體,以便另一個線程可以給這個線程一些工作。 以下是它將如何發出信號:

void AssignWork(WorkItem work)
{
    pthread_mutex_lock(&work_mutex);

    add_work_to_queue(work);           // put work item on queue

    pthread_cond_signal(&work_cv);     // wake worker thread

    pthread_mutex_unlock(&work_mutex);
}

注意你需要互斥體來保護工作隊列。 注意條件變量本身不知道是否有工作。 也就是說,一個條件變量必須與一個條件相關聯,該條件必須由您的代碼維護,並且由於它在線程之間共享,所以它必須受互斥體保護。

https://src-bin.com

我正在閱讀pthread.h ; 與條件變量相關的函數(如pthread_cond_wait(3) )需要一個互斥量作為參數。 為什麼? 據我所知,我將創建一個互斥體來作為這個參數? 這個互斥量應該做什麼?


Answer #1

並非所有的條件變量函數都需要一個互斥鎖:只有等待操作才會執行。 信號和廣播操作不需要互斥。 條件變量也不會永久與特定的互斥鎖相關聯; 外部互斥量不保護條件變量。 如果條件變量具有內部狀態,例如等待線程的隊列,則必須使用條件變量中的內部鎖來保護它。

等待操作將條件變量和互斥鎖結合在一起,因為:

  • 一個線程鎖定了互斥鎖,通過共享變量評估了一些表達式,發現它是錯誤的,因此需要等待。
  • 線程必須從擁有互斥體原子地移動到等待條件。

因此,等待操作將互斥量和條件作為參數:以便它可以管理線程從擁有互斥體到等待的原子轉移,以便線程不會成為丟失的喚醒競爭條件的犧牲品。

如果一個線程放棄了一個互斥體,然後等待一個無狀態的同步對象,但是以非原子化的方式發生喚醒失敗的情況:當線程不再擁有該鎖時,存在一個時間窗口尚未開始等待對象。 在此窗口期間,另一個線程可以進入,使等待狀態成為真實,發出無狀態同步信號,然後消失。 無狀態的對像不記得它是有信號的(它是無狀態的)。 因此,原始線程在無狀態同步對像上進入睡眠狀態,並且不會喚醒,即使它所需的條件已成為真實:喚醒丟失。

條件變量等待函數通過確保調用線程被註冊以在放棄互斥之前可靠地捕獲喚醒,從而避免了喚醒丟失。 如果條件變量等待函數沒有將互斥體作為參數,這將是不可能的。


Answer #2

如果你想要一個條件變量的實例,我在課堂上做了一個練習:

#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "unistd.h"

int compteur = 0;
pthread_cond_t varCond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex_compteur;

void attenteSeuil(arg)
{
    pthread_mutex_lock(&mutex_compteur);
        while(compteur < 10)
        {
            printf("Compteur : %d<10 so i am waiting...\n", compteur);
            pthread_cond_wait(&varCond, &mutex_compteur);
        }
        printf("I waited nicely and now the compteur = %d\n", compteur);
    pthread_mutex_unlock(&mutex_compteur);
    pthread_exit(NULL);
}

void incrementCompteur(arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex_compteur);

            if(compteur == 10)
            {
                printf("Compteur = 10\n");
                pthread_cond_signal(&varCond);
                pthread_mutex_unlock(&mutex_compteur);
                pthread_exit(NULL);
            }
            else
            {
                printf("Compteur ++\n");
                compteur++;
            }

        pthread_mutex_unlock(&mutex_compteur);
    }
}

int main(int argc, char const *argv[])
{
    int i;
    pthread_t threads[2];

    pthread_mutex_init(&mutex_compteur, NULL);

    pthread_create(&threads[0], NULL, incrementCompteur, NULL);
    pthread_create(&threads[1], NULL, attenteSeuil, NULL);

    pthread_exit(NULL);
}

Answer #3

我沒有發現其他答案像本頁一樣簡潔易讀。 通常等待的代碼看起來像這樣:

mutex.lock()
while(!check())
    condition.wait()
mutex.unlock()

wait()包裝在互斥鎖中有三個原因:

  1. 如果沒有互斥量,另一個線程可能會在wait()之前signal() wait() ,我們會錯過這個喚醒。
  2. 通常, check()取決於來自另一個線程的修改,因此無論如何您都需要相互排除。
  3. 以確保最高優先級的線程首先進行(互斥隊列允許調度程序決定接下來的人)。

第三點並不總是一個問題 - 歷史背景是從文章與這個談話聯繫起來的。

關於這種機制通常會提到虛假喚醒(即喚醒等待的線程,而不調用signal() )。 但是,這些事件由循環check()


Answer #4

當您調用pthread_cond_wait時,該互斥鎖應該被鎖定; 當你調用它時,它會自動釋放互斥鎖,然後阻塞條件。 一旦條件發出信號,它會再次以原子方式鎖定並返回。

如果需要,這允許執行可預測的調度,因為執行信令的線程可以等待,直到互斥量被釋放以進行處理,然後發信號通知該條件。


Answer #5

這似乎是一個具體的設計決策,而不是概念上的需求。

根據pthreads文檔,互斥體沒有分開的原因是通過將它們結合在一起可以顯著提高性能,並且他們期望如果不使用互斥體,由於共同的競爭條件,它幾乎總是要完成的。

https://linux.die.net/man/3/pthread_cond_wait

互斥體和條件變量的特點

有人建議將互斥量的獲取和釋放與狀態等待分開。 這被拒絕了,因為這是操作的組合性質,事實上,這有助於實時實現。 那些實現可以以對調用者透明的方式原子地移動條件變量和互斥鎖之間的高優先級線程。 這可以防止額外的上下文切換,並在等待線程發送信號時提供更多確定性的互斥量採集。 因此,公平性和優先級問題可以由調度規則直接處理。 此外,當前的條件等待操作符合現有的做法。





condition-variable