3

我有一个与此问题相关但未得到回答的问题:

boost shared_mutex(多次读取/一次写入)的示例?

我了解当操作在成员函数范围内时,排他锁是如何工作的。我的问题是,按引用返回函数呢?考虑以下(伪代码):

class A {
    shared_mutex _mutex;
    std::string _name;

public:
    const std::string& name() const {shared_lock(_mutex); return _name;}
}

然后假设我在代码中做了这样的事情:

A obj;
if (obj.name().length() >0) { ... };

我的直觉告诉我这可能不是线程安全的,因为在调用 length() 函数时互斥锁已经超出了范围,但我不知道。

我想我也是在问大局,如果我试图使对象成为线程安全的,我应该完全避免通过引用返回吗?难道它不能让某人做这样的事情:

A obj;
std::string& s = obj.name();
(at this point lock is out of scope)
s = "foo";  // does this change the original object's member since it is a reference?
4

3 回答 3

2

它不是线程安全的,因为在返回mutex之后将被释放,name()并且另一个线程可以_name在调用之前或期间std::string::length()或之后开始修改,并且_name在进入.{...}if

要使对象线程安全,请确保对对象的所有访问都在锁定_name时发生。mutex这意味着确保对的引用_name不返回给调用者或不传递给提供给(未在原始代码中发布的虚构成员函数)的成员函数的回调,A因为此回调可以缓存_name未来的地址,不同步, 利用。

相反,按值返回或传递(并创建_name互斥锁锁定时的副本):

// Must be mutable to be modifiable
// in a const member function.
mutable shared_mutex _mutex;

std::string name() const
{
    // Must not create a temporary shared_lock,
    // otherwise the lock will be released immediately.
    // So give the shared_lock a name.
    shared_lock lk(_mutex);
    return _name;
}
于 2013-03-25T20:35:10.383 回答
1

只需更改此功能声明:

const std::string& name() const ...

对此。

std::string name() const ...

然后,您将返回该字符串的副本。 (而且由于它是副本,因此不需要是const

该代码将是线程安全的,并且任何编译器优化都将保持这种安全性。

于 2013-03-25T20:39:45.143 回答
0

大概出于性能原因,您正在使用 shared_mutex (而不是普通互斥锁),但是复制字符串会很昂贵。也可能大括号内的代码if (obj.name().length() >0) { ... }将进一步操作字符串。如果是这种情况,那么您需要获取整个操作的互斥锁,否则您将遇到竞争条件。(例如,您复制name,取其长度,然后其他人将其缩短name为长度为 0。大括号中的代码现在将对 的长度做出错误的假设name。)

如果可能,您可以考虑将要操作字符串的代码设为class A.

class A {
  shared_mutex _mutex;
  std::string _name;

public:
   rettype my_operation() {
     shared_lock lk(_mutex);
     if (_name.length() >0) { ... };
   }
};

还有其他选择,但它们都涉及在您依赖不变持有的整个时间段内持有共享锁。

于 2013-03-25T21:56:31.720 回答