簡體   English   中英

使用std :: atomic和std :: condition_variable,Sync是不可靠的

[英]Sync is unreliable using std::atomic and std::condition_variable

在用C ++ 11編寫的分布式作業系統中,我使用以下結構實現了一個fence(即工作線程池外部的線程可能要求阻塞,直到完成所有當前計划的作業):

struct fence
{
    std::atomic<size_t>                     counter;
    std::mutex                              resume_mutex;
    std::condition_variable                 resume;

    fence(size_t num_threads)
        : counter(num_threads)
    {}
};

實現fence的代碼如下所示:

void task_pool::fence_impl(void *arg)
{
    auto f = (fence *)arg;
    if (--f->counter == 0)      // (1)
        // we have zeroed this fence's counter, wake up everyone that waits
        f->resume.notify_all(); // (2)
    else
    {
        unique_lock<mutex> lock(f->resume_mutex);
        f->resume.wait(lock);   // (3)
    }
}

如果線程在一段時間內進入圍欄,這種方法非常有效。 然而,如果他們幾乎同時嘗試這樣做,似乎有時會發生在原子遞減(1)和開始條件var(3)的等待之間,線程產生CPU時間而另一個線程將計數器遞減到零( 1)並解雇cond。 var(2)。 這導致前一個線程在(3)中永遠等待,因為它已經被通知后開始等待它。

讓事情變得可行的黑客就是在(2)之前進行10毫秒的睡眠,但這顯然是不可接受的。

關於如何以高效的方式解決這個問題的任何建議?

您的診斷是正確的,此代碼很容易以您描述的方式丟失條件通知。 即在一個線程鎖定互斥鎖之后但在等待條件變量之前,另一個線程可能會調用notify_all(),以便第一個線程錯過該通知。

一個簡單的解決方法是在遞減計數器之前鎖定互斥鎖,同時通知:

void task_pool::fence_impl(void *arg)
{
    auto f = static_cast<fence*>(arg);
    std::unique_lock<std::mutex> lock(f->resume_mutex);
    if (--f->counter == 0) {
        f->resume.notify_all();
    }
    else do {
        f->resume.wait(lock);
    } while(f->counter);
}

在這種情況下,計數器不必是原子的。

在通知之前鎖定互斥鎖的額外獎勵(或懲罰,取決於觀點)是(從這里 ):

pthread_cond_broadcast()或pthread_cond_signal()函數可以由線程調用,無論它當前是否擁有調用pthread_cond_wait()或pthread_cond_timedwait()的線程在等待期間與條件變量相關聯的互斥鎖; 但是, 如果需要可預測的調度行為,則該互斥鎖應由調用pthread_cond_broadcast()或pthread_cond_signal()的線程鎖定

關於while循環(從這里 ):

可能會發生pthread_cond_timedwait()或pthread_cond_wait()函數的虛假喚醒。 由於從pthread_cond_timedwait()或pthread_cond_wait()返回並不意味着有關此謂詞的值的任何內容,因此應在返回時重新評估謂詞。

為了保持原子操作的更高性能而不是完整的互斥鎖,您應該將等待條件更改為鎖定,檢查和循環。

所有條件等待都應該以這種方式完成。 條件變量甚至有一個等待的第二個參數,它是一個謂詞函數或lambda。

代碼可能如下所示:

void task_pool::fence_impl(void *arg)
{
    auto f = (fence *)arg;
    if (--f->counter == 0)      // (1)
        // we have zeroed this fence's counter, wake up everyone that waits
        f->resume.notify_all(); // (2)
    else
    {
        unique_lock<mutex> lock(f->resume_mutex);
        while(f->counter) {
            f->resume.wait(lock);   // (3)
        }
    }
}

暫無
暫無

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

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