写时复制 (COW) 惯用法的线程安全实现?

发布于 2024-10-05 02:04:43 字数 2606 浏览 0 评论 0原文

任何人都可以向我指出 Copy-on-write (COW)< 的线程安全实现/a> 习语? 此网站上的示例代码看起来不错 - 是它是线程安全的吗?

如果有人想知道我将用它做什么:我有一个 Foo 类,它有一个 std::map 成员。 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_;
};

这是类Foo包含这样一个地图 RcMap,使用写入时复制机制:

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 对象的构造函数或赋值运算符。但也许有人可以发现线程安全错误?

Can anyone point me to a thread-safe implementation of the Copy-on-write (COW) idiom? The sample code on this site looks good -- is it thread-safe?

In case anyone is wondering what I will be using it for: I have a Foo class that has a std::map<int,double> member. Foo objects are copied very frequently in my code, but the copies rarely modify the contained map. I found that COW gives me a 22% performance boost, compared to copying the whole map contents in the Foo copy constructor, but my COW implementation crashes when multiple threads are used.

UPDATE:

Okay, here is the code, reduced to a minimal example, since you asked for it:

First, a reference-counting map:

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_;
};

And here is the class Foo that contains such a map RcMap, using a Copy-on-write mechanism:

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_;
};

I haven't yet been able to reproduce the crash using this minimal example, but in my original code it happens when I use the copy constructor or assignment operator of Foo objects in parallel. But maybe someone can spot the thread-safety bug?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

掌心的温暖 2024-10-12 02:04:43

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

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

COW is inherently thread-safe, since the original is essentially immutable, and only the thread that induces the copy sees the copied version in the process of being created. You only need to watch for two things:

  1. Make sure the original doesn't get deleted by another thread while the copy is occurring. This an orthogonal problem, though (e.g., you could use thread-safe ref-counting).
  2. Make sure all the read operations you perform while copying are thread-safe. This is rarely a problem, but sometimes a read might populate a cache, for instance.
    • In fact, if this assumption is violated, that's a problem with the read operation not being thread-safe, and will probably affect more code than just the COW.
街角迷惘 2024-10-12 02:04:43

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

RcMap's reference counts need to be made atomic in order to be thread safe. In G++ 4.1, you an use the atomic builtins to implement this.

_畞蕅 2024-10-12 02:04:43

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

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

If you're copying a mutable map (it looks like you are), then don't decrease the reference count on the original object until after the copy is complete. (Because otherwise you may wind up allowing writes to the object you're copying, thereby breaking thread safety.)

Better yet, use a fully immutable map implementation (that makes copies and updates even cheaper by using shared substructure) if you can. There's a previous question on this topic that's currently unanswered.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文