4

假设我有这样的代码:

#include "boost/thread/mutex.hpp"

using boost::mutex;
typedef mutex::scoped_lock lock;

mutex mut1, mut2;

void Func() {
 // ...
}

void test_raiicomma_1() {
 lock mut1_lock(mut1);
 Func();
}

void test_raiicomma_2() {
 (lock(mut1)), Func();
}

void test_raiicomma_3() {
 (lock(mut1)), (lock(mut2)), Func(); // Warning!
}

int main()
{
 test_raiicomma_1();
 test_raiicomma_2();
 test_raiicomma_3();
 return 0;
}

如果函数test_raiicomma_1() 是从多个线程调用的,它会锁定一个互斥体以防止任何其他线程同时调用Func()。互斥体在构造变量时被锁定,mut1_lock在超出范围并被破坏时释放。

这工作得很好,但作为一种风格,需要为持有锁的临时对象命名让我很恼火。该函数test_raiicomma_2()试图通过初始化锁对象并Func()在一个表达式中调用该函数来避免这种情况。

直到表达式结束才调用临时对象析构函数是否正确, afterFunc()已返回?(如果是这样,你认为使用这个成语是否值得,或者在单独的语句中声明锁总是更清楚?)

如果该函数test_raiicomma_3()需要锁定两个互斥锁,那么互斥锁在调用之前会按顺序锁定Func(),然后释放,但不幸的是可能会以任一顺序释放,这是否正确?

4

3 回答 3

4

在 Func() 返回后,直到表达式结束才调用临时对象析构函数是否正确?

保证调用构造函数和析构函数,因为它们有副作用,并且破坏只会发生在完整表达式的末尾。

我相信它应该工作

如果函数 test_raiicomma_3() 需要锁定两个互斥体,那么互斥体在调用 Func() 之前会按顺序锁定,然后释放,但不幸的是可能会以任一顺序释放,这是否正确?

逗号总是从左到右进行评估,并且范围内的自动变量总是以创建的相反顺序被销毁,所以我认为它甚至可以保证它们也以(正确的)顺序释放

正如 litb 在评论中指出的那样,您需要大括号,否则您的表达式将被解析为声明。

(如果是这样,你认为使用这个成语是否值得,或者在单独的语句中声明锁总是更清楚?)

我不这么认为,不,为了获得非常非常少的收益而感到困惑......我总是使用非常明确的锁和非常明确的范围(通常在一个块中额外的 {} ),没有“特殊”的体面的线程安全代码就足够困难了代码,并且在我看来需要非常清晰的代码。

当然是 YMMW :)

于 2009-09-08T11:49:42.927 回答
3

虽然不必给出有意义的名称可以让你摆脱负担,但它会增加一项任务,即找出代码应该做什么给代码读者的负担——在决定是否test_raiicomma_3真的按照编写者的方式工作的任务之上似乎已经猜到了。

假设一段代码写了一次,读了 10、100 或 1000 次,写所有的锁真的有那么难吗?

于 2009-09-08T11:43:50.460 回答
0

互斥体将按从左到右的顺序创建,并在完整表达式结束时释放。你可以写:

 // parentheses tells that it is full expression and set order explicitly
 ( ( lock(mut1), lock(mut2) ), Func() ); 

根据 C++ 标准 5.18/1,互斥锁将按正确顺序销毁:

逗号运算符从左到右分组。
表达式:
赋值表达式
表达式,赋值表达式

一对用逗号分隔的表达式从左到右求值,左边表达式的值被丢弃。左值到右值 (4.1)、数组到指针 (4.2) 和函数到指针 (4.3) 标准转换不适用于左表达式。左侧表达式的所有副作用 (1.9),除了临时变量 (12.2) 的破坏,都在评估右侧表达式之前执行。结果的类型和值是右操作数的类型和值;如果它的右操作数是,则结果是一个左值。

于 2009-09-08T11:45:00.613 回答