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