![](/img/trans.png)
[英]std::memory_order_relaxed atomicity with respect to the same atomic variable
[英]std::atomic_bool for cancellation flag: is std::memory_order_relaxed the correct memory order?
我有一個從套接字讀取並生成數據的線程。 在每次操作之后,線程檢查std::atomic_bool
標志以查看它是否必須提前退出。
為了取消操作,我將取消標志設置為true
,然后在工作線程對象上調用join()
。
線程代碼和取消函數看起來像這樣:
std::thread work_thread;
std::atomic_bool cancel_requested{false};
void thread_func()
{
while(! cancel_requested.load(std::memory_order_relaxed))
process_next_element();
}
void cancel()
{
cancel_requested.store(true, std::memory_order_relaxed);
work_thread.join();
}
std::memory_order_relaxed
是否使用了原子變量的正確內存順序?
只要在cancel_requested
標志和其他任何內容之間沒有依賴關系,您就應該是安全的。
如圖所示的代碼看起來沒問題, 假設您僅使用cancel_requested
來加速關閉,而且還有一個有序關閉的設置,例如隊列中的sentinel條目(當然隊列本身是同步的)。
這意味着您的代碼實際上如下所示:
std::thread work_thread;
std::atomic_bool cancel_requested{false};
std::mutex work_queue_mutex;
std::condition_variable work_queue_filled_cond;
std::queue work_queue;
void thread_func()
{
while(! cancel_requested.load(std::memory_order_relaxed))
{
std::unique_lock<std::mutex> lock(work_queue_mutex);
work_queue_filled_cond.wait(lock, []{ return !work_queue.empty(); });
auto element = work_queue.front();
work_queue.pop();
lock.unlock();
if (element == exit_sentinel)
break;
process_next_element(element);
}
}
void cancel()
{
std::unique_lock<std::mutex> lock(work_queue_mutex);
work_queue.push_back(exit_sentinel);
work_queue_filled_cond.notify_one();
lock.unlock();
cancel_requested.store(true, std::memory_order_relaxed);
work_thread.join();
}
如果我們那么遠,那么cancel_requested
也可能成為常規變量,代碼甚至變得更簡單。
std::thread work_thread;
bool cancel_requested = false;
std::mutex work_queue_mutex;
std::condition_variable work_queue_filled_cond;
std::queue work_queue;
void thread_func()
{
while(true)
{
std::unique_lock<std::mutex> lock(work_queue_mutex);
work_queue_filled_cond.wait(lock, []{ return cancel_requested || !work_queue.empty(); });
if (cancel_requested)
break;
auto element = work_queue.front();
work_queue.pop();
lock.unlock();
process_next_element(element);
}
}
void cancel()
{
std::unique_lock<std::mutex> lock(work_queue_mutex);
cancel_requested = true;
work_queue_filled_cond.notify_one();
lock.unlock();
work_thread.join();
}
memory_order_relaxed
通常很難推理,因為它模糊了順序執行代碼的一般概念。 因此,赫伯在他的原子武器談話中解釋說,它的用處非常非常有限。
注意std::thread::join()
本身充當兩個線程之間的內存屏障。
這段代碼是否正確取決於很多事情。 最重要的是它取決於“正確”的確切含義。 據我所知,您顯示的代碼位不會調用未定義的行為(假設您的work_thread
和cancel_requested
實際上沒有按照上面的代碼段建議的順序進行初始化,因為您可能會讓線程讀取未初始化的值。原子)。 如果您需要做的就是更改該標志的值並讓線程最終在某個時刻看到新值而不管其他任何可能發生的值,那么std::memory_order_relaxed
就足夠了。
但是,我看到你的工作線程調用了process_next_element()
函數。 這表明工作線程有一些機制可以接收要處理的元素。 在處理完所有元素后,我沒有看到線程退出的任何方法。 當沒有下一個元素可用時, process_next_element()
做什么? 它會立即返回嗎? 在這種情況下,你有一個忙等待更多的輸入或取消,這將工作但可能不理想。 或者process_next_element()
內部調用一些阻塞的函數,直到一個元素可用為止!? 如果是這種情況,那么取消線程必須首先設置取消標志,然后做任何需要確保下一個元素調用你的線程可能阻止返回。 在這種情況下,在阻塞調用返回后,線程永遠不會看到取消標志是很重要的。 否則,您可能會返回調用,返回循環,仍然讀取舊的取消標志,然后再次調用process_next_element()
。 如果保證process_next_element()
再次返回,那么你沒事。 如果不是這樣,那你就陷入了僵局。 所以我認為它在技術上取決於process_next_element()
確切含義。 人們可以想象的實現process_next_element()
你將有可能需要比寬松內存順序的更多。 但是,如果您已經有一個獲取要處理的新元素的機制,為什么甚至使用單獨的取消標記? 您可以通過相同的機制簡單地處理取消,例如,讓它返回具有特殊值的下一個元素,或者根本不返回任何元素來表示取消處理並導致線程返回而不是依賴於單獨的標志......
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.