[英]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.