为什么第一段代码不会导致死锁?

3
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <thread>
std::mutex mtx;

void func2() {
    mtx.lock();
    std::cout << "here is 2" << std::endl;
    mtx.unlock();
}

void func1() {
    mtx.lock();
    std::cout << "here is 1" << std::endl;
    func2();
    mtx.unlock();
}

int main() {
    func1();
}

但如果我将主函数修改如下,它会引起死锁

int main() {
    std::thread t1(func1);
    t1.join();
}

我用 "g++ test.cpp -std=c++11 -lpthread" 编译了这两个文件。


要出现真正的死锁,至少需要两个线程,每个线程都持有至少两个互斥锁。因此,即使您的代码存在一些问题,它也不可能出现死锁,因为您只有一个线程和一个互斥锁。 - Marek R
5
根据文档:如果已经拥有互斥锁的线程调用lock,行为是未定义的:例如,程序可能会死锁。因此,两个代码片段都表现出未定义的行为。 - G.M.
为什么第一种情况的行为不同可能与Windows上的CriticalSection的怪癖有关。没错,是Windows吧? - Swift - Friday Pie
我认为一些实现可以检测到程序从未调用任何可能启动线程的函数。在这种情况下,所有对互斥函数的调用都可以被优化掉,因为没有互斥锁会被争用(否则会导致 UB,就像你的程序一样),因此没有任何互斥锁调用会产生任何可观察的效果。这可能是你注意到的特定类型 UB 的解释。 - Nate Eldredge
1个回答

7

在同一线程中连续调用lock(未解锁互斥量)是未定义的行为。来自cppreference:

如果由已经拥有该互斥量的线程调用了lock,则行为未定义:例如,程序可能会死锁。

它可能会死锁,也可能不会。 行为是未定义的。

请注意,std::recursive_mutex可以多次锁定(虽然仅限于某些未指定的限制)。 但是,需要递归互斥锁的代码更加复杂。 在您的示例中,删除func1中的锁定会更容易,因为仅在该互斥量已被锁定时才调用它。 通常情况下并不那么简单。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接