xiaoweiChen / CPP-Concurrency-In-Action-2ed-2019

:book: 作为对《C++ Concurrency in Action - SECOND EDITION》的中文翻译。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

6.2.1死锁不理解的地方

haidaidaihai opened this issue · comments

”当调用持有一个锁的用户代码时,有两个地方可能会死锁:拷贝构造或移动构造(①,③)和拷贝赋值或移动赋值操作⑤;还有一个潜在死锁的地方在于用户定义的new操作符。无论是以直接调用栈的成员函数的方式,还是在成员函数进行操作时,对已经插入或删除的数据进行操作的方式,对锁进行获取,都可能造成死锁。不过,用户要对栈负责,当栈未对数据进行拷贝或分配时,用户就不能随意的将其添加到栈中。“
请问这里①,③,还有⑤为什么会发生死锁,每次调用函数开头就已经上锁了,如果为空就直接抛出,那为什么还会死锁。没明白,能否举例说明哈。谢谢!

所有成员函数都使用std::lock_guard<>保护数据,所以栈成员函数才是“线程安全”的。当然,构造与析构函数不是“线程安全”的,不过也没关系,因为构造与析构只有一次。调用一个不完全构造对象或是已销毁对象的成员函数,无论在那种编程方式下都不可取。所以,用户就要保证在栈对象完成构建前,其他线程无`法对其进行访问;并且,一定要保证在栈对象销毁后,所有线程都要停止对其进行访问。

其实在你引用段的下一段,就给出了解释。

这里使用std::lock_guard<>对成员函数进行保护,但是当成员函数中有异常发生,那么当前代码的处理方式,就是直接抛出异常。
这里要明确的是,异常抛出不会对std::lock_guard<>构造出来实例进行销毁,这也就是为什么文中说这里会出现死锁的原因。

因为这个栈只是一个部件,不能确定外部调用代码是什么样的。
如果抛出的异常不被捕获,那么会让程序直接终止运行,一旦终止运行,那么就无所谓是否死锁了。
如果抛出的异常被外部代码进行捕获,并且进行了一些操作,但没有对这个成员函数中锁进行释放,也就是销毁std::lock_guard<>的对象,那么下次有线程调用成员函数则会出现死锁的情况。

1.lock_guard本身是把mutex封装了的,符合raii,那如果拷贝中发生异常抛出,应该就会自动销毁以及创建的lock_guard对象吧。那样不就解锁了吗
A: 这个问题的最重要的问题就是“应该就会自动销毁以及创建的lock_guard对象吧”这句话的“应该”这个词。这个情况只是你猜想的,具体编译器会采取什么样的行为,我想你并没有尝试过。异常只会抛出而已,不会去销毁任何和异常无关的东西,所以就需要对异常进行处理。我想,这个线程安全栈的代码还是比较完整的,你可以尝试写个出现异常的例子,自己调试一下看看,确定一下是不是会有 自动销毁 的行为产生。如果你的代码确实会自动销毁,那就很有趣了,可以发出来一起看看。

2.文中说出现死锁的地方pop和push函数也并未在函数中写try-catch结构体,那不是应该符合你说的异常不被捕获程序直接停止运行,无所谓死锁吗
A: 我觉得你没有看懂我的意思。如果异常不处理,程序就会终止运行,所以无所谓死锁。只有在运行的程序中讨论死锁才有意义。