Closed YZH-bot closed 1 year ago
在 lock 的文档中我们可以看到:
If lock is called by a thread that already owns the mutex, the behavior is undefined: for example, the program may deadlock.
翻译:如果一个线程已经拥有锁,那么再调用一次 lock 是未定义行为:例如,可能发生死锁。
这是很容易理解的,因为在多线程编程模型中我们知道,同一个线程连续 lock 两次会导致死锁。但是标准说了这是未定义行为,而不是明确规定会发生死锁,所以同一个调用两次 lock 可能发生死锁,也可能发生其他事情:包括但不限于抛出异常后推出,段错误,除零错误,代码的执行顺序紊乱,改变其他变量的值,甚至电脑着火,都有可能发生,当然也可能不产生任何错误,正常执行下去。
这就是未定义行为,只要你触犯了,他可以发生任何事,C++ 标准都不保护你。标准规定为未定义的,编译器都可以随便处理,发生任何事情都是你自己的责任,只不过对于 double-lock 而言大概率是“死锁”这一种情况罢了,正如空指针解引用大概率是”段错误“这一种情况一样。
之所以标准不规定是为了不要限制编译器厂商的创造力,例如官方文档后面举了个例子:“一个编译器实现可以检测到这种问题,并抛出 system_error 异常,方便用户调试。”
An implementation that can detect the invalid usage is encouraged to throw a std::system_error with error condition resource_deadlock_would_occur instead of deadlocking.
在 try_lock 的文档中我们还可以看到:
If try_lock is called by a thread that already owns the mutex, the behavior is undefined.
翻译:如果一个线程已经拥有锁,那么再调用一次 try_lock 也是未定义行为。
没想到8,try_lock 也不允许。
你是不是已经想当然地认为 lock 会死锁,try_lock 就不会,即使是同一个线程,也能安全返回 false?
你是不是已经想当然地认为 try_lock 的内部实现是这样的了:
bool try_lock() {
if (!this->m_locked) {
lock();
return true;
}
return false;
}
如果标准都规定死了内部实现,不给编译器自由度,人家还怎么优化了?首先你“想当然”的这种 try_lock 内部实现就是不线程安全的。
记得我说的吗?发生未定义行为时编译器可以做任何事,包括不产生任何错误正常执行,但是打印一个错误的答案给你。
永远不要想当然,多看看 cppreference,官方文档说是未定义行为,就碰都不要去碰。不要依赖未定义行为,不要跟我说“汇编就是这样”,“我平时都是能稳定触发死锁/段错误/改变其他变量值”的。调试代码如果遇到发生任何离谱的行为时,首先检查你的代码,是不是有什么地方“想当然”了,触发未定义行为了,那发生任何事情都是合理的。
所以如何修复这个未定义行为呢?标准说同一个线程多次调用 lock 或 try_lock 是未定义行为,但是他没说多个线程分别调用啊?所以你可以创建另一个线程去调用 try_lock,达到你“想当然”的那种“教学结果”。
#include <cstdio>
#include <mutex>
#include <thread>
std::mutex mtx1;
int main() {
if (mtx1.try_lock())
printf("succeed\n");
else
printf("failed\n");
std::thread t1([] { // 同一个线程不能 double-lock,也不能 double-try_lock,但没说另一个线程不可以
if (mtx1.try_lock())
printf("succeed\n");
else
printf("failed\n");
});
t1.join(); // join 在 unlock 前,保证 t1 里的 try_lock 是在 main 持有锁的情况下完成执行的
mtx1.unlock();
return 0;
}
运行结果:
succeed
failed
Get,感谢指出👍
我在ubuntu20.04上尝试跑了一下07这个代码,出来结果有点奇怪,请问这是什么原因?
输出结果都是succeed,按理应该先是succeed后failed