c++ - std::shared_ptr 的 use_count() 周围的完整内存屏障是否会使它成为可靠的计数器?

标签 c++ multithreading shared-ptr memory-barriers reference-counting

我正在实现一个线程安全的“惰性同步”集合,作为由 shared_ptr 连接的节点的链表。该算法来自“多处理器编程的艺术”。我正在添加一个 is_empty() 函数,它需要与现有函数线性化:contains()、add()、remove()。在下面的代码中,您可以看到 remove 是一个两步过程。首先,它通过设置 marked = nullptr 来“惰性”标记节点,然后它物理地移动链表 next 指针。

修改类以支持 is_empty()

template <class T>
class LazySet : public Set<T> {
    public:
      LazySet ();
      bool contains (const T&) const;
      bool is_empty ()         const;
      bool add      (const T&);
      bool remove   (const T&);
    private:
      bool validate(const std::shared_ptr<Node>&, const std::shared_ptr<Node>&);
      class Node;
      std::shared_ptr<Node> head;
      std::shared_ptr<bool> counter; //note: type is unimportant, will never change true/fase
};

template <class T>
class LazySet<T>::Node {
    public:
      Node ();
      Node (const T&);
      T key;
      std::shared_ptr<bool> marked; //assume initialized to = LazySet.counter
                                    // nullptr means it's marked; otherwise unmarked
      std::shared_ptr<Node> next;
      std::mutex mtx;
};

相关修改方法支持is_empty

template <class T>
bool LazySet<T>::remove(const T& k) {
    std::shared_ptr<Node> pred;
    std::shared_ptr<Node> curr;
    while (true) {
        pred = head;
        curr = atomic_load(&(head->next));
        //Find window where key should be in sorted list
        while ((curr) && (curr->key < k)) {
            pred = atomic_load(&curr);
            curr = atomic_load(&(curr->next));
        }
        //Aquire locks on the window, left to right locking prevents deadlock
        (pred->mtx).lock();
        if (curr) { //only lock if not nullptr
            (curr->mtx).lock();
        }
        //Ensure window didn't change before locking, and then remove
        if (validate(pred, curr)) {
            if (!curr) { //key doesn't exist, do nothing
                //## unimportant ##
            } else { //key exists, remove it
                atomic_store(&(curr->marked), nullptr); //logical "lazy" remove
                atomic_store(&(pred->next), curr->next) //physically remove
                (curr->mtx).unlock();
                (pred->mtx).unlock();
                return true;
            }
        } else {
            //## unlock and loop again ##
        }
    }
}

template <class T>
bool LazySet<T>::contains(const T& k) const {
    std::shared_ptr<Node> curr;
    curr = atomic_load(&(head->next));
    //Find window where key should be in sorted list
    while ((curr) && (curr->key < k)) {
        curr = atomic_load(&(curr->next));
    }
    //Check if key exists in window
    if (curr) {
        if (curr->key == k) { //key exists, unless marked
            return (atomic_load(&(curr->marked)) != nullptr);
        } else { //doesn't exist
            return false;
        }
    } else { //doesn't exist
        return false;
    }
}

Node.marked 最初是一个普通的 bool,LazySet.counter 并不存在。使它们成为 shared_ptrs 的选择是能够以原子方式修改节点数量上的计数器和节点上的延迟删除标记。在 remove() 中同时修改两者对于 is_empty()contains() 可线性化是必要的。 (它不能是一个单独的 bool 标记和 int 计数器,没有双宽 CAS 或其他东西。)我希望用 shared_ptr 的 use_count() 函数实现计数器,但在多线程上下文中它只是一个近似值由于 relaxed_memory_order

我知道独立的栅栏通常是不好的做法,而且我不太熟悉它们的使用。 但如果我像下面那样实现 is_empty,栅栏是否会确保它不再是近似值,而是可靠计数器的精确值?

template <class T>
bool LazySet<T>::is_empty() const {
    // ## SOME FULL MEMORY BARRIER
    if (counter.use_count() == 1) {
        // ## SOME FULL MEMORY BARRIER
        return true
    }
    // ## SOME FULL MEMORY BARRIER
    return false
}

我问是因为LWG Issue 2776说:

We can't make use_count() reliable without adding substantially more fencing.

最佳答案

宽松的内存顺序不是这里的问题。 use_count 不“可靠”,因为在返回值时,它可能已经更改。获取值本身不存在数据竞争,但是没有什么可以阻止在基于该值的任何条件语句之前修改该值。

所以你不能对它做任何依赖于它的值仍然有意义的事情(除了,如果你仍然持有一个 shared_ptr 实例,那么使用计数将不会转到 0)。使其可靠的唯一方法是防止它被更改。所以你需要有一个互斥体。

而且那个互斥体必须锁定,不仅仅是在 use_count 调用和使用时,而且每次你​​分发这些 shared_ptr 之一时,你从中获取 use_count

关于c++ - std::shared_ptr 的 use_count() 周围的完整内存屏障是否会使它成为可靠的计数器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56318483/

相关文章:

C++ 变暖标准 vector

c++ - dumb data object 包含所有公共(public)值 c++,这是正确的吗

c++ - 读入非数值时的 cin 无限循环

c# - 在 WCF 回调中收到的消息乱序

c++ - 将 vector<boost::shared_ptr<Foo>> 转换为 vector<Foo*> ,这可能吗?

c++ - std::shared_ptr 的 vector 未释放内存

c++ - 如何更改 QT Creator 中的调试器?

multithreading - 面包店算法(死锁?)

linux - 线程亲和性与进程亲和性

c++ - shared_ptr 的缺点