[英]C++11 thread hangs on locking mutex
使用C ++ 11 std::thread
, std::mutex
,我正在編寫一個簡單的工作線程。 但是,我在鎖定std::mutex
了一個奇怪的掛起問題,看起來兩個線程(主線程和工作線程)都試圖鎖定互斥鎖,但兩者都被阻止。
這是完整的代碼
#include <thread>
#include <condition_variable>
#include <memory>
#include <iostream>
#include <list>
std::condition_variable cv;
std::mutex m;
std::thread t;
bool shouldExit = false;
std::list<int> jobs;
void thread_func()
{
std::unique_lock<std::mutex> lock(m);
while (!shouldExit) {
while (jobs.empty() && !shouldExit) {
cv.wait(lock);
}
// Do some stuff
if (jobs.empty()) {
continue;
}
// Get a job and do something with it
if (!lock.owns_lock()) {
lock.lock(); // <<<< Worker thread hang here
}
auto j = std::move(jobs.front());
jobs.pop_front();
lock.unlock();
std::cout << "Do something with job " << j << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
int main()
{
t = std::thread(thread_func);
for (int i = 1; i < 100; ++i) {
std::cout << "Push to job " << i << std::endl;
{
std::lock_guard<std::mutex> lock(m); // <<<< main thread hang here
jobs.push_back(i);
cv.notify_one();
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// To wait for thread exit
shouldExit = true;
cv.notify_one();
t.join();
return 0;
}
我正在使用Ubuntu 14.04上面的命令編譯代碼
g++ -std=c++11 -g -O0 -pthread -o testthread testthread.cpp
執行結果通常如下:
$ ./testthread
Push to job 1
Do something with job 1
Push to job 2
Do something with job 2
Push to job 3
Push to job 4
有趣的是,當我將主線程中的lock_guard
-1ms的一行代碼移動到如下所示的lock_guard
,問題就消失了。
for (int i = 1; i < 100; ++i) {
std::cout << "Push to job " << i << std::endl;
{
std::lock_guard<std::mutex> lock(m);
jobs.push_back(i);
cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(1)); // Moved into lock_guard
}
}
我無法弄清楚為什么。 你能幫忙解釋一下代碼的行為以及我做錯了什么嗎?
[更新]我知道以某種方式重寫工作線程可以解決問題。 但是我仍然想在原始代碼中知道當兩個線程鎖定互斥鎖但兩者都被阻塞時究竟發生了什么。
這是不確定的行為叫cv.wait
與lock
沒有被鎖定。 添加此斷言:
while (!shouldExit) {
assert(lock.owns_lock()); // <------ add this
while (jobs.empty() && !shouldExit) {
cv.wait(lock);
}
libc ++將從wait
if !lock.owns_lock()
拋出,但我不知道其他實現會做什么。
您的代碼中存在嚴重且經典的錯誤....
首先,請參閱帶注釋/編號的注釋。 我會提到他們
void thread_func()
{
std::unique_lock<std::mutex> lock(m); // <---- {1}
while (!shouldExit) { // <---- {2}
while (jobs.empty() && !shouldExit) { // <---- {3}
cv.wait(lock);
}
// Do some stuff
if (jobs.empty()) {
continue;
}
if (!lock.owns_lock()) {
lock.lock(); // <---- {4}
}
auto j = std::move(jobs.front());
jobs.pop_front();
lock.unlock(); // <---- {5}
std::cout << "Do something with job " << j << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
{1}這很好......看到你在{5}中擊敗了這個目標
{2} shouldExit
應該是一個原子布爾。 否則你將有競爭條件
{3}在執行的某個時刻,將在未持有鎖的情況下測試此條件,請參閱{5}中的unlock語句。 因此,你還有另一種競爭條件。
{4}使用解鎖的互斥鎖,在您測試鎖定的時間和發出鎖定的時間之間,可以獲取互斥鎖,從而使其永久等待。
{5}使互斥鎖解鎖以便下次執行循環......嚴重的競爭條件和死鎖將會發生。
只需添加lock.lock()
的最后一行到你的thread_func()
像這樣....
void thread_func()
{
.....more code omitted
........
lock.unlock();
std::cout << "Do something with job " << j << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
lock.lock(); //YOUR NEW LINE
}
}
添加將循環恢復到在進入之前被鎖定的互斥鎖的原始狀態....注意,有另一個代碼路徑來到達循環的條目...在哪里有continue
語句...每當std::condition_variable::wait()
返回,鎖總是重新鎖定,所以仍然保持不變性...
現在您的代碼有效!! 好極了!!! ......但它仍然聞起來很多! std::cout
是線程安全的,但輸出不同步,因此,您可能有交錯字符...
使用std::cout
解決問題如何正確執行? 檢查此代碼(另請參閱注釋)
void thread_func()
{
std::unique_lock<std::mutex> lock(m);
while (!shouldExit) // this is redundant, so I removed it in the final code
{
while (jobs.empty() && !shouldExit)
{
cv.wait(lock, []{ return !jobs.empty(); } );
}
// Do some stuff
auto j = std::move(jobs.front());
jobs.pop_front();
//cout is thread-safe but not synchronized
//std::cout << "Do something with job " << j << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
在我所知道的最常見的情況下,最好在std::condition_variable::wait()
測試你的“准備好繼續”條件。
把它們放在一起為你......這是一個更好的版本
#include <thread>
#include <condition_variable>
#include <memory>
#include <iostream>
#include <list>
#include <atomic>
std::condition_variable cv;
std::mutex m;
std::mutex mxa; //for std::cout locking
std::thread t;
std::atomic<bool> shouldExit;
std::list<int> jobs;
namespace detail
{
std::ostream& safe_print()
{
return std::cout;
}
template<typename T, typename... Args>
std::ostream& safe_print(T&& t, Args&&... args)
{
std::cout << t;
return safe_print(std::forward<Args>(args)...);
}
}
template<typename... Args>
std::ostream& println(Args&&... args)
{
std::lock_guard<std::mutex> lck(mxa);
auto&& x = detail::safe_print(std::forward<Args>(args)...);
std::cout << std::endl;
return x;
}
void thread_func()
{
std::unique_lock<std::mutex> lock(m);
while (jobs.empty() && !shouldExit)
{
cv.wait(lock, []{ return !jobs.empty(); } );
}
// Do some stuff
auto j = std::move(jobs.front());
jobs.pop_front();
//std::cout << "Do something with job " << j << std::endl;
println("Do something with job ", j);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
int main()
{
shouldExit = false;
//safe_print("This is really funny ", 43, '\n');
t = std::thread(thread_func);
for (int i = 1; i < 100; ++i)
{
//std::cout << "Push to job " << i << std::endl;
println("Push to Job ", i);
{
std::lock_guard<std::mutex> lock(m); // <<<< main thread doesn't hang here again
jobs.push_back(i);
cv.notify_one();
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// To wait for thread exit
shouldExit = true;
cv.notify_one();
t.join();
return 0;
}
嘗試重寫你的工作線程,如下所示:
void thread_func()
{
while (!shouldExit)
{
int j ;
{
std::unique_lock<std::mutex> lock(m); // lock object inside while
while (jobs.empty() && !shouldExit) {
cv.wait(lock);
}
// Do some stuff
if (jobs.empty()) {
continue;
}
j = jobs.front();
jobs.pop_front();
} // lock goes out of scope
std::cout << "Do something with job " << j << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
正如您所看到的,j不再被移動,您可以在int j ;
之后封裝該部分int j ;
在函數內部獲得相同的效果。
重寫的主要思想是避免搞亂lock的成員函數,並通過讓構造函數/析構函數進行鎖定作業來使用它。
你有一個問題:
while (jobs.empty() && !shouldExit) {
cv.wait(lock);
}
// Do some stuff
if (jobs.empty()) {
continue;
}
當你醒來時,你擁有鎖。 然而,通過調用continue
你沒有任何釋放它的機會。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.