英文:
C++: Adding a sleep causes deadlock
问题
以下是您要翻译的代码部分:
我有这段代码。
std::thread t1(func1);
std::this_thread::sleep_for( std::chrono::seconds( 2 ) );
std::thread t2(func2);
std::thread t3(func3);
t1.join();
t2.join();
t3.join();
在这段代码中,t1锁定了互斥锁m1,而t2正在等待获取m1的锁。这段代码按预期工作。
现在,如果在t1中添加5秒的休眠,程序会崩溃,并显示以下错误 -
terminate called after throwing an instance of 'std::system_error'
what(): Resource deadlock avoided
Aborted (core dumped)
以下是所有线程的回溯信息 -
(gdb) thread 1
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1 0x00007fa9eae827f1 in __GI_abort () at abort.c:79
#2 0x00007fa9eb875957 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007fa9eb87bae6 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007fa9eb87ab49 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007fa9eb87b4b8 in __gxx_personality_v0 () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x00007fa9eb243573 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1
#7 0x00007fa9eb243ad1 in _Unwind_RaiseException () from /lib/x86_64-linux-gnu/libgcc_s.so.1
#8 0x00007fa9eb87bd47 in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#9 0x00007fa9eb877a23 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#10 0x000055d2f9bbe02f in std::unique_lockstd::mutex::lock (this=0x55d2fbe062f8) at /usr/include/c++/7/bits/std_mutex.h:264
(gdb) thread 2
#0 0x00007fa9ebd7ed2d in __GI___pthread_timedjoin_ex (threadid=140367767017216, thread_return=0x0, abstime=0x0, block=
#1 0x00007fa9eb8a6933 in std::thread::join() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2 0x000055d2f9bb6e20 in main (argc=
(gdb)
(gdb) bt
#0 0x00007fa9ebd87d50 in __GI___nanosleep (requested_time=requested_time@entry=0x7fa9eae40d30, remaining=remaining@entry=0x7fa9eae40d30) at ../sysdeps/unix/sysv/linux/nanosleep.c:28
#1 0x000055d2f9bbeaf5 in std::this_thread::sleep_for<long, std::ratio<1l, 1000l> > (__rtime=...) at /usr/include/c++/7/thread:373
我的问题是 -
1. 为什么会崩溃?它说是避免了死锁,但我无法理解死锁是什么?
2. C++是否具有内置的死锁检测?可以通过某些标志禁用吗?
添加了可复现问题的代码 -
```cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <unordered_map>
class Worker {
public:
Worker() : workerLock(workerMutex, std::defer_lock) {}
~Worker() {
workerLock.lock();
workerLock.unlock();
}
void acquireWorkerLock() {
workerLock.lock();
}
void releaseWorkerLock() {
workerLock.unlock();
}
void work() {
std::cout << "I'm working" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
}
private:
std::mutex workerMutex;
std::unique_lock<std::mutex> workerLock;
};
class WorkerManager {
public:
void newWorker(std::string name) {
const std::lock_guard<std::mutex> lock(managerMutex);
bool workerAlreadyPresent = ( workers.find( name ) != workers.end() );
if( !workerAlreadyPresent ) {
Worker *worker = new Worker();
workers.insert( {name, worker} );
}
}
void work(std::string name) {
std::unique_lock<std::mutex> lock(managerMutex);
auto workerIt = workers.find( name );
if( workerIt != workers.end() ) {
Worker *worker = workerIt->second;
worker->acquireWorkerLock();
lock.unlock();
worker->work();
worker->releaseWorkerLock();
}
}
void removeWorker(std::string name) {
std::unique_lock<std::mutex> lock(managerMutex);
auto workerIt = workers.find( name );
if( workerIt != workers.end() ) {
Worker *worker = workerIt->second;
workers.erase(workerIt);
lock.unlock();
delete worker;
}
}
private:
std::unordered_map<std::string, Worker*> workers;
std::mutex managerMutex;
};
WorkerManager wm;
void func1() {
wm.work("Foo");
std::cout << "Work done by worker Foo\n";
}
void func2() {
wm.removeWorker("Foo");
std::cout << "Worker removed Foo\n";
}
void func3() {
wm.newWorker("Bar");
std::cout << "Worker added Bar\n";
}
int main() {
wm.newWorker("Foo");
std::thread t1(func1);
std::this_thread::sleep_for( std::chrono::seconds( 2 ) );
std::thread t2(func2);
std::thread t3(func3);
t1.join();
<details>
<summary>英文:</summary>
I have this code.
std::thread t1(func1);
std::this_thread::sleep_for( std::chrono::seconds( 2 ) );
std::thread t2(func2);
std::thread t3(func3);
t1.join();
t2.join();
t3.join();
In this code t1 takes a lock on mutex m1 and t2 is waiting to get the lock on m1. This code works as expected.
Now if I add a sleep for 5 seconds in t1, the program crashes with the following error -
terminate called after throwing an instance of 'std::system_error'
what(): Resource deadlock avoided
Aborted (core dumped)
Here are the backtraces of all the threads -
(gdb) thread 1
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1 0x00007fa9eae827f1 in __GI_abort () at abort.c:79
#2 0x00007fa9eb875957 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007fa9eb87bae6 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007fa9eb87ab49 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007fa9eb87b4b8 in __gxx_personality_v0 () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x00007fa9eb243573 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1
#7 0x00007fa9eb243ad1 in _Unwind_RaiseException () from /lib/x86_64-linux-gnu/libgcc_s.so.1
#8 0x00007fa9eb87bd47 in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#9 0x00007fa9eb877a23 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#10 0x000055d2f9bbe02f in std::unique_lock<std::mutex>::lock (this=0x55d2fbe062f8) at /usr/include/c++/7/bits/std_mutex.h:264
(gdb) thread 2
#0 0x00007fa9ebd7ed2d in __GI___pthread_timedjoin_ex (threadid=140367767017216, thread_return=0x0, abstime=0x0, block=<optimized out>) at pthread_join_common.c:89
#1 0x00007fa9eb8a6933 in std::thread::join() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2 0x000055d2f9bb6e20 in main (argc=<optimized out>, argv=<optimized out>) at tests/benchmark-tests/benchmarkTest.cpp:294
(gdb)
(gdb) bt
#0 0x00007fa9ebd87d50 in __GI___nanosleep (requested_time=requested_time@entry=0x7fa9eae40d30, remaining=remaining@entry=0x7fa9eae40d30) at ../sysdeps/unix/sysv/linux/nanosleep.c:28
#1 0x000055d2f9bbeaf5 in std::this_thread::sleep_for<long, std::ratio<1l, 1000l> > (__rtime=...) at /usr/include/c++/7/thread:373
My questions are -
1. Why is this crashing? It is saying deadlock avoidance but I am not able to understand what the deadlock is?
2. Does C++ have inbuild deadlock detection? Can that be disabled by some flag?
Added the code which reproduces the issue -
#include <iostream>
#include <thread>
#include <mutex>
#include <unordered_map>
class Worker {
public:
Worker() : workerLock(workerMutex, std::defer_lock) {}
~Worker() {
workerLock.lock();
workerLock.unlock();
}
void acquireWorkerLock() {
workerLock.lock();
}
void releaseWorkerLock() {
workerLock.unlock();
}
void work() {
std::cout << "I'm working" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
}
private:
std::mutex workerMutex;
std::unique_lock<std::mutex> workerLock;
};
class WorkerManager {
public:
void newWorker(std::string name) {
const std::lock_guard<std::mutex> lock(managerMutex);
bool workerAlreadyPresent = ( workers.find( name ) != workers.end() );
if( !workerAlreadyPresent ) {
Worker *worker = new Worker();
workers.insert( {name, worker} );
}
}
void work(std::string name) {
std::unique_lock<std::mutex> lock(managerMutex);
auto workerIt = workers.find( name );
if( workerIt != workers.end() ) {
Worker *worker = workerIt->second;
worker->acquireWorkerLock();
lock.unlock();
worker->work();
worker->releaseWorkerLock();
}
}
void removeWorker(std::string name) {
std::unique_lock<std::mutex> lock(managerMutex);
auto workerIt = workers.find( name );
if( workerIt != workers.end() ) {
Worker *worker = workerIt->second;
workers.erase(workerIt);
lock.unlock();
delete worker;
}
}
private:
std::unordered_map<std::string, Worker*> workers;
std::mutex managerMutex;
};
WorkerManager wm;
void func1() {
wm.work("Foo");
std::cout << "Work done by worker Foo\n";
}
void func2() {
wm.removeWorker("Foo");
std::cout << "Worker removed Foo\n";
}
void func3() {
wm.newWorker("Bar");
std::cout << "Worker added Bar\n";
}
int main() {
wm.newWorker("Foo");
std::thread t1(func1);
std::this_thread::sleep_for( std::chrono::seconds( 2 ) );
std::thread t2(func2);
std::thread t3(func3);
t1.join();
t2.join();
t3.join();
return 0;
}
Backtrace for the above code -
(gdb) thread 1
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1 0x00007f79d03387f1 in __GI_abort () at abort.c:79
#2 0x00007f79d0bac957 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007f79d0bb2ae6 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007f79d0bb1b49 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007f79d0bb24b8 in __gxx_personality_v0 () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x00007f79d0918573 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1
#7 0x00007f79d0918ad1 in _Unwind_RaiseException () from /lib/x86_64-linux-gnu/libgcc_s.so.1
#8 0x00007f79d0bb2d47 in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#9 0x00007f79d0baea23 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#10 0x00005653edd0f690 in std::unique_lock<std::mutex>::lock (this=0x5653eeb65e98) at /usr/include/c++/7/bits/std_mutex.h:264
#11 0x00005653edd0f048 in Worker::~Worker (this=0x5653eeb65e70, __in_chrg=<optimized out>) at test.cpp:10
#12 0x00005653edd0f443 in WorkerManager::removeWorker (this=0x5653edf16140 <wm>, name="Foo") at test.cpp:57
(gdb) thread 2
#0 0x00007f79d06f1d2d in __GI___pthread_timedjoin_ex (threadid=140161156749056, thread_return=0x0, abstime=0x0, block=<optimized out>) at pthread_join_common.c:89
#1 0x00007f79d0bdd933 in std::thread::join() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2 0x00005653edd0eb0e in main () at test.cpp:89
(gdb)
(gdb) thread 3
#0 0x00007f79d06fad50 in __GI___nanosleep (requested_time=0x7f79cff58cb0, remaining=0x7f79cff58cb0) at ../sysdeps/unix/sysv/linux/nanosleep.c:28
#1 0x00005653edd0fb2d in std::this_thread::sleep_for<long, std::ratio<1l, 1000l> > (__rtime=...) at /usr/include/c++/7/thread:373
#2 0x00005653edd0f135 in Worker::work (this=0x5653eeb65e70) at test.cpp:21
#3 0x00005653edd0f339 in WorkerManager::work (this=0x5653edf16140 <wm>, name="Foo") at test.cpp:46
</details>
# 答案1
**得分**: 4
你不能通过相同的 `std::unique_lock` 两次锁定一个互斥锁。在你的情况下,如果正在进行工作,工作线程的互斥锁已经通过它的 `workerLock` 锁定。然后,如果另一个线程尝试删除工作线程,它会在工作线程的析构函数中对相同的唯一锁调用 `lock`。引用自 [cppreference](https://en.cppreference.com/w/cpp/thread/unique_lock/lock):
> **异常**
>
> 如果互斥锁已经被此 `unique_lock` 锁定(换句话说,`owns_lock` 为真),则 std::system_error...
我认为,唯一锁或任何锁卫士不应该在单一代码范围(块)之外使用。
这是一个建议的 `Worker`,它不将唯一锁用作成员变量:
```cpp
class Worker {
public:
~Worker() {
std::lock_guard<std::mutex> workerLock(workerMutex);
}
void acquireWorkerLock() {
workerMutex.lock();
}
void releaseWorkerLock() {
workerMutex.unlock();
}
void work() {
std::cerr << "I'm working" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
}
private:
std::mutex workerMutex;
}
这个问题可能与此相关:unique_lock across threads?。Howard Hinnant 提供的主要回答中有一个简短的引用:
unique_lock
不应该同时从多个线程访问。它不是为以这种方式线程安全设计的。相反,多个unique_lock
(局部变量)引用同一个全局mutex
。只有mutex
本身设计为可以同时被多个线程访问。
英文:
You cannot lock a mutex through the same std::unique_lock
twice. In your case, if the work is being done, the worker's mutex has been lock through its workerLock
. Then, if another thread tries to remove the worker, it calls lock
on the same unique lock in the worker's destructor. Quoting from the cpprefernce:
> Exceptions
>
> If the mutex is already locked by this unique_lock
(in other words, owns_lock is true), std::system_error...
IMO, a unique lock or any lock guard is not meant to be used outside of a single code scope (block).
Here is a proposed Worker
that doesn't use a unique lock as a member variable:
class Worker {
public:
~Worker() {
std::lock_guard<std::mutex> workerLock(workerMutex);
}
void acquireWorkerLock() {
workerMutex.lock();
}
void releaseWorkerLock() {
workerMutex.unlock();
}
void work() {
std::cerr << "I'm working" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
}
private:
std::mutex workerMutex;
}
This question might be relevant: unique_lock across threads?. A short quote from the main answer provided by Howard Hinnant:
> unique_lock
should not be accessed from multiple threads at once. It was not designed to be thread-safe in that manner. Instead, multiple unique_lock
s (local variables) reference the same global mutex
. Only the mutex
itself is designed to be accessed by multiple threads at once.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论