线程安全的复制构造函数/赋值运算符

ipa*_*dop 7 c++ thread-safety c++11

假设我们想A使用一个类使线程安全std::mutex.我的复制构造函数和赋值运算符与下面的代码类似:

#include <mutex>

class A {
private:
  int i;
  mutable std::mutex mtx;

public:
  A() : i(), mtx() { }

  A(const A& other) : i(), mtx()
  {
    std::lock_guard<std::mutex> _lock(other.mtx);
    i = other.i;
  }

  A& operator=(const A& other)
  {
    if (this!=&other) {
      std::lock_guard<std::mutex> _mylock(mtx), _otherlock(other.mtx);
      i = other.i;
    }
    return *this;
  }

  int get() const
  {
    std::lock_guard<std::mutex> _mylock(mtx);
    return i;
  }
};
Run Code Online (Sandbox Code Playgroud)

我不认为它有任何问题,除了在other复制之前被另一个线程销毁的可能性,我可以处理.

我的问题是我没有在任何地方看到这种模式,所以我不知道人们是否只是没有需要,或者由于我目前看不到的原因,这显然是错误的.

谢谢

注意:

这只是一个例子.我可以拥有任意类型的任意数量的成员变量,它不必只是一个int.

在Martin York对可能出现死锁的评论之后,这是一个使用复制和交换的更新版本(复制省略也是可能的,但它无法有效处理自我分配案例).

我也把int改为T,所以人们不能认为它是POD.

template<typename T>
class A {
private:
  T t;
  mutable std::mutex mtx;

public:
  A() : t(), mtx() { }

  A(const A& other) : t(), mtx()
  {
    std::lock_guard<std::mutex> _lock(other.mtx);
    t = other.t;
  }

  A& operator=(const A& other)
  {
    if (this!=&other) {
      A tmp(other);
      std::lock_guard<std::mutex> _lock(mtx);
      std::swap(t, tmp.t);
    }
    return *this;
  }

  T get() const
  {
    std::lock_guard<std::mutex> _lock(mtx);
    return t;
  }

};
Run Code Online (Sandbox Code Playgroud)

How*_*ant 5

老问题,新答案:

恕我直言,处理原始复制赋值运算符死锁问题的更好方法是:

  A& operator=(const A& other)
  {
    if (this!=&other) {
      std::unique_lock<std::mutex> _mylock(mtx, std::defer_lock),
                                   _otherlock(other.mtx, std::defer_lock);
      std::lock(_mylock, _otherlock);
      i = other.i;
    }
    return *this;
  }
Run Code Online (Sandbox Code Playgroud)

即用于std::lock(L1, L2)同时锁定两个互斥锁而不用担心死锁。这很可能是比复制/交换成语性能高,特别是如果构件数据由std::vectorstd::string或包含载体和/或串的类型。

在 C++1y(我们希望 y 是 4)中,有一个新的<shared_mutex>头文件提供读/写锁定功能,可以提供性能提升(特定用例需要进行性能测试以确认这一点)。以下是它的使用方法:

#include <mutex>
#include <shared_mutex>

class A {
private:
  int i;
  mutable std::shared_mutex mtx;

public:
  A() : i(), mtx() { }

  A(const A& other) : i(), mtx()
  {
    std::shared_lock<std::shared_mutex> _lock(other.mtx);
    i = other.i;
  }

  A& operator=(const A& other)
  {
    if (this!=&other) {
      std::unique_lock<std::shared_mutex> _mylock(mtx, std::defer_lock);
      std::shared_lock<std::shared_mutex> _otherlock(other.mtx, std::defer_lock);
      std::lock(_mylock, _otherlock);
      i = other.i;
    }
    return *this;
  }

  int get() const
  {
    std::shared_lock<std::shared_mutex> _mylock(mtx);
    return i;
  }
};
Run Code Online (Sandbox Code Playgroud)

即这与原始代码非常相似(修改std::lock为我在上面所做的使用)。但是成员互斥锁类型现在std::shared_mutex不是std::mutex. 并且在保护 a 时const A(并假设除互斥锁之外没有可变成员),只需要将互斥锁锁定在“共享模式”。使用shared_lock<shared_mutex>. 当您需要以“独占模式”锁定互斥锁时,您可以使用unique_lock<shared_mutex>, 或lock_guard<shared_mutex>,并以与使用此功能相同的方式使用std::mutex.

特别要注意的是,现在许多线程可以同时从同一个复制构造A,甚至同一个复制分配A。但是一次只有一个线程仍然可以复制分配同一个线程A

  • 无论哪种情况,只需要一个普通的互斥锁。问题是:如果允许受保护数据的多个读取器同时读取,代码是否可以运行得更快?在“A&amp; operator=(A other)”的情况下,如果您想从中进行分配,这仍然涉及复制构造“A”。如果复制构造函数使用独占互斥体,则一次只能从一个线程进行复制构造。如果它使用带有读锁的共享互斥体,则可以一次从多个线程进行复制构造。但这并不意味着共享互斥锁总是更快。共享互斥体可能具有更高的开销。 (2认同)