[英]C++11 thread hangs on locking mutex
Using C++11 std::thread
, std::mutex
, I'm writing a simple worker thread. 使用C ++ 11
std::thread
, std::mutex
,我正在编写一个简单的工作线程。 However, I got a strange hang issue in locking std::mutex
, which looks like both threads (main and worker thread) are trying to lock a mutex, but both are blocked. 但是,我在锁定
std::mutex
了一个奇怪的挂起问题,看起来两个线程(主线程和工作线程)都试图锁定互斥锁,但两者都被阻止。
Here's the full code 这是完整的代码
#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;
}
I'm compiling the code using below command on Ubuntu 14.04 我正在使用Ubuntu 14.04上面的命令编译代码
g++ -std=c++11 -g -O0 -pthread -o testthread testthread.cpp
The executing result is typically like this: 执行结果通常如下:
$ ./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
The interesting part is, when I move one line code of sleeping-1ms in main thread into the lock_guard
like below, the issue is gone. 有趣的是,当我将主线程中的
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
}
}
I could not figure out why. 我无法弄清楚为什么。 Could you help to explain the behavior of the code and what I did wrong?
你能帮忙解释一下代码的行为以及我做错了什么吗?
[Update] I know re-writing the worker thread in certain way could fix the issue. [更新]我知道以某种方式重写工作线程可以解决问题。 But I still would like to know in the original code what exactly happens when the two threads are locking the mutex but both are blocked.
但是我仍然想在原始代码中知道当两个线程锁定互斥锁但两者都被阻塞时究竟发生了什么。
It is undefined behavior to call cv.wait
with lock
not locked. 这是不确定的行为叫
cv.wait
与lock
没有被锁定。 Add this assert: 添加此断言:
while (!shouldExit) {
assert(lock.owns_lock()); // <------ add this
while (jobs.empty() && !shouldExit) {
cv.wait(lock);
}
libc++ will throw from the wait
if !lock.owns_lock()
, but I don't know what other implementations will do. libc ++将从
wait
if !lock.owns_lock()
抛出,但我不知道其他实现会做什么。
You have serious and classic bugs in your code.... 您的代码中存在严重且经典的错误....
First, please see the annotated/numbered comments. 首先,请参阅带注释/编号的注释。 I will refer to them
我会提到他们
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} This is good.... see you defeating the aim of this in {5} {1}这很好......看到你在{5}中击败了这个目标
{2} shouldExit
should be an atomic bool. {2}
shouldExit
应该是一个原子布尔。 Else you will have race conditions 否则你将有竞争条件
{3} At some point in execution, this condition will be tested while not holding the lock, see the unlock statement in {5}. {3}在执行的某个时刻,将在未持有锁的情况下测试此条件,请参阅{5}中的unlock语句。 Hence, you have yet another race condition.
因此,你还有另一种竞争条件。
{4} With an unlocked mutex, between the time you test the lock and the time you issue lock, the mutex could be acquired, causing this to perpetually wait here. {4}使用解锁的互斥锁,在您测试锁定的时间和发出锁定的时间之间,可以获取互斥锁,从而使其永久等待。
{5} Makes the mutex unlocked for the next execution of the loop... serious race conditions and deadlock will happen. {5}使互斥锁解锁以便下次执行循环......严重的竞争条件和死锁将会发生。
Just add lock.lock()
to the last line to your thread_func()
只需添加
lock.lock()
的最后一行到你的thread_func()
like this.... 像这样....
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
}
}
The addition restores the loop back to the original state of a mutex being locked before entry.... Note that there is another code path to reach the entry of the loop... where you had continue
statement... Whenever a std::condition_variable::wait()
returns, the lock is always relocked, so invariant is still maintained... 添加将循环恢复到在进入之前被锁定的互斥锁的原始状态....注意,有另一个代码路径来到达循环的条目...在哪里有
continue
语句...每当std::condition_variable::wait()
返回,锁总是重新锁定,所以仍然保持不变性...
Now your code works!! 现在您的代码有效!! Yay!!!
好极了!!! ... But it still smells a lot!
......但它仍然闻起来很多!
std::cout
is thread-safe but the output isn't synchronized, therefore, you may have interleaved characters... std::cout
是线程安全的,但输出不同步,因此,您可能有交错字符...
Sidelining the problem with std::cout
How to do it properly? 使用
std::cout
解决问题如何正确执行? Check this code (also please see the comments) 检查此代码(另请参阅注释)
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));
}
}
In most common cases I know of, its always better to test your "ready to proceed" conditions inside std::condition_variable::wait()
. 在我所知道的最常见的情况下,最好在
std::condition_variable::wait()
测试你的“准备好继续”条件。
To put it all together for you.... Here is a better version 把它们放在一起为你......这是一个更好的版本
#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;
}
Try rewrite your worker thread like this: 尝试重写你的工作线程,如下所示:
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));
}
}
As you can see j is no longer moved, you could encapsulate the section after int j ;
正如您所看到的,j不再被移动,您可以在
int j ;
之后封装该部分int j ;
inside a function to get the same effect. 在函数内部获得相同的效果。
The main idea of the rewrite is avoid messing with lock's member function and use it by letting constructor/destructor making the locking job. 重写的主要思想是避免搞乱lock的成员函数,并通过让构造函数/析构函数进行锁定作业来使用它。
You have a problem here: 你有一个问题:
while (jobs.empty() && !shouldExit) {
cv.wait(lock);
}
// Do some stuff
if (jobs.empty()) {
continue;
}
When you wake up, you own the lock. 当你醒来时,你拥有锁。 However, by calling
continue
you loose any chance of releasing it. 然而,通过调用
continue
你没有任何释放它的机会。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.