- 点击查看原文 The Risks of Mutexes。
- 文章主要通过介绍直接调用 mutex 的 lock 和 unlock 容易导致死锁,引出 lock(另外的博文,结尾链接)。
The Risks of Mutexes
Mutex的风险
Usage of mutexes seems extremely simple. There is a critical section in the code, which can only be accessed by a single thread at any point of time. It’s ensured by a mutex m. The calls m.lock() and m.unlock() guarantee this exclusivity. But, the devil is in the details.
- mutex 的用法似乎非常简单。代码中有一个关键部分,该部分在任何时间点都只能由单个线程访问。它是由一个 mutex m 确保的。m.lock() 和 m.unlock() 的调用保证了这种排他性。但是,细节决定成败。
Deadlock
The different names for deadlocks are frightening. Some call them deadly embrace (hug of death 😃? or kiss of death. But wait, what is a deadlock?
- 死锁的不同名称是可怕的。有些称它们为致命的拥抱(死亡拥抱)?或死亡之吻。但是等等,什么是死锁?
Deadlock
A deadlock is a state, in which at least two threads are blocked, because each thread is waiting for release of some resource with which other thread works, before it releases its own resource.
The result of a deadlock is total standstill. The Thread and usually the whole program is blocked forever. It is easy to produce a deadlock. Curious?
- 死锁是一种状态,其中至少有两个线程被阻塞,因为每个线程在释放它们自己的资源前 都在等待其他线程释放正在使用的资源。
- 死锁的结果是完全停滞。线程和通常整个程序永远被阻塞。产生死锁很容易。好奇?
Exceptions and unknown code
std::mutex m;
m.lock();
sharedVariable= getVar();
m.unlock();
In case the unknown code in the function getVar() throws an exception, m.unlock() will not be called. Every attempt to to ask for the mutex m will fail and the program will block. Forever. But that is not the only issue with that piece of code. It calls some (unknown to us) function get.Var(), while m.lock() is active. What will happen if the function getVar() tries to get the same lock? Of course, you know it. A deadlock.
Do you want to have a more visual example?
- 例子中,如果函数 getVar() 抛出异常,m.unlock()将不会被调用。每个尝试请求 mutex m 都将失败,程序将永远阻塞。但是,这并不是该代码唯一的问题。在 m.lock() 处于活动状态时,函数 getVar() 调用了一些(对我们来说未知的)代码。 如果函数 getVar() 尝试获取相同的锁将发生什么?当然你知道的,死锁。
- 是否要有一个更直观的例子?
Lock mutexes in different order
Thread 1 and Thread 2 need access to two resources in order to finish their work. Unfortunately, they ask for the resources which are protected by two mutexes in different order. In this case the thread executions will interleave in such a way that thread 1 gets mutex 1, then thread 2 gets mutex 2, and we have a standstill. Each thread wants to get the other’s mutex. For this, thread has to wait for the release of the resource.
It’s easy to express the picture in code.
-
线程1 和线程2 需要访问两个资源才能完成其工作。不幸的是,他们需要的资源被两个 mutex 以不同的顺序保护。这种情况下,线程将以下面的方式交错执行:线程1 获得 mutex1, 然后线程2 获得 mutex2, 然后程序被阻塞。每个线程想得到另一个线程的 mutex。为此,线程必须等待资源的释放。
-
很容易用代码来描述上述图片。
// deadlock.cpp #include <iostream> #include <chrono> #include <mutex> #include <thread> struct CriticalData{ std::mutex mut; }; void deadLock(CriticalData& a, CriticalData& b){ a.mut.lock(); std::cout << "get the first mutex" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(1)); b.mut.lock(); std::cout << "get the second mutex" << std::endl; // do something with a and b a.mut.unlock(); b.mut.unlock(); } int main(){ CriticalData c1; CriticalData c2; std::thread t1([&]{deadLock(c1,c2);}); std::thread t2([&]{deadLock(c2,c1);}); t1.join(); t2.join(); }
Thread t1 and thread t2 call the function deadlock (line 12 - 20). To process deadlock, both functions need the CriticalData c1 and c2 (line 27 and 28). Because the objects c1 and c2 must be protected from shared access, they have a mutex (to keep this example code short and simple CriticalData doesn’t have any other methods or members apart from mutex)
Only a sleep about 1 Millisecond in line 16, and we have the deadlock.
- 线程 t1 和 线程 t2 调用函数 deadlock (12-20行)。要处理 deadlock,这两个函数都需要 CriticalData c1 和 c2 ( 27- 28行)。由于对象 c1 和 c2 不能被共享访问,它们 有 一个 mutex (要保持此例代码简短 且 简单的 CriticalData 除了 mutex 没有其他 方法或成员)
- 在 16 行 睡眠 1 毫秒,然后我们得到一个死锁。
The only choice now is the press CTRL+C to kill the process.
- 唯一的办法是 按下 CTRL+C 结束程序。
What’s next?
Honestly, the example will not boost your confidence in writing multithreading programs. Additionally, the complexity will increase to the power of 2 which each new mutex. The solution to the problem are locks, because they encapsulate mutexes in a safe manner.
- 老实说,这个例子并不会增强你编写多线程程序的信心。此外,每增加一个 mutex,复杂性将以 2 的幂级增长。问题的解决方案是 locks, 因为它们以安全的方式封装 mutex。 点击 这里。