【问题标题】:Thread-safe implementation of the Copy-on-write (COW) idiom?写时复制(COW)习语的线程安全实现?
【发布时间】:2011-05-17 14:38:23
【问题描述】:

谁能指出Copy-on-write (COW) 成语的线程安全实现? this site 上的示例代码看起来不错——它是线程安全的吗?

如果有人想知道我将使用它做什么:我有一个 Foo 类,它有一个 std::map<int,double> 成员。 Foo 对象在我的代码中被非常频繁地复制,但这些副本很少修改包含的 map。我发现与在 Foo 复制构造函数中复制整个地图内容相比,COW 给了我 22% 的性能提升,但是当使用多个线程时,我的 COW 实现会崩溃。

更新:

好的,这里是代码,简化为一个最小的例子,因为你要求它:

首先,引用计数图:

class RcMap {                             
 public:
  typedef std::map<int,double> Container;
  typedef Container::const_iterator const_iterator;
  typedef Container::iterator iterator;

  RcMap() : count_(1) {}

  RcMap(const RcMap& other) : count_(1) {
    m_ = other.Get();
  }

  unsigned Count() const { return count_; }
  unsigned IncCount() { return ++count_; }
  unsigned DecCount() {
    if(count_ > 0) --count_;
    return count_;
  }
  void insert(int i, double d) {
    m_.insert(std::make_pair(i,d));
  }
  iterator begin() { return m_.begin(); }
  iterator end() { return m_.end(); }
  const_iterator begin() const { return m_.begin(); }
  const_iterator end() const { return m_.end(); }

 protected:
  const Container& Get() const { return m_; }

 private:
  void operator=(const RcMap&); // disallow

  Container m_;
  unsigned count_;
};

这里是包含这样一个映射RcMap 的类Foo,使用写时复制机制:

class Foo {
 public:
  Foo() : m_(NULL) {}

  Foo(const Foo& other) : m_(other.m_) {
    if (m_) m_->IncCount();
  }

  Foo& operator= (const Foo& other) {
    RcMap* const old = m_;
    m_ = other.m_;
    if(m_ != 0)
      m_->IncCount();
    if (old != 0 && old->DecCount() == 0) {
      delete old;
    }
    return *this;
  }

  virtual ~Foo() {
    if(m_ != 0 && m_->DecCount() == 0){
      delete m_;
      m_ = 0;
    }
  }

  const RcMap& GetMap() const {
    if(m_ == 0)
      return EmptyStaticRcMap();
    return *m_;
  }

  RcMap& GetMap() {
    if(m_ == 0)
      m_ = new RcMap();
    if (m_->Count() > 1) {
      RcMap* d = new RcMap(*m_);
      m_->DecCount();
      m_ = d;
    }
    assert(m_->Count() == 1);
    return *m_;
  }

  static const RcMap& EmptyStaticRcMap(){
    static const RcMap empty;
    return empty;
  }

 private:
  RcMap* m_;
};

我还不能使用这个最小的示例重现崩溃,但在我的原始代码中,当我并行使用 Foo 对象的复制构造函数或赋值运算符时会发生这种情况。但也许有人能发现线程安全漏洞?

【问题讨论】:

  • 有人可以解释如何使用比较和交换成语来实现它,正如这里所暗示的那样 (en.wikipedia.org/wiki/Copy-on-write)?
  • 如果您的 COW 崩溃,如果您可以发布一个说明问题的最小代码示例,这可能会有所帮助。另外,你会遇到什么样的崩溃?
  • 好的,我添加了一个最小的代码示例。崩溃发生在 delete m_Foo 析构函数中。
  • 如果您可以使用 C++0x,我建议您将 move c'tor 与您的 COW 实现进行比较,看看哪个更快。
  • @Zach:这听起来不适合他的用例,因为他似乎真的想要一个对象的副本。

标签: c++ multithreading memory idioms


【解决方案1】:

COW 本质上是线程安全的,因为原始版本本质上是不可变的,并且只有诱导复制的线程在创建过程中看到复制的版本。你只需要注意两件事:

  1. 确保在进行复制时原件不会被其他线程删除。不过,这是一个正交问题(例如,您可以使用线程安全的引用计数)。
  2. 确保在复制时执行的所有读取操作都是线程安全的。这很少会成为问题,但有时读取可能会填充缓存。
    • 事实上,如果违反了这个假设,那就是读取操作不是线程安全的问题,并且可能会影响更多的代码,而不仅仅是 COW。

【讨论】:

    【解决方案2】:

    RcMap 的引用计数需要原子化以保证线程安全。在 G++ 4.1 中,您可以使用 atomic builtins 来实现这一点。

    【讨论】:

    • 修复了崩溃问题。感谢这个简单的解决方案!
    【解决方案3】:

    如果您正在复制一个可变映射(看起来确实如此),那么在复制完成之前不要减少原始对象的引用计数。 (因为否则您可能会允许写入您正在复制的对象,从而破坏线程安全。)

    如果可以的话,更好的是,使用完全不可变的映射实现(通过使用共享子结构使复制和更新更便宜)。有一个关于此主题的 previous question 目前未得到答复。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-01
      • 1970-01-01
      • 2019-03-12
      • 2013-08-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多