繁体   English   中英

std :: shared_ptr的use_count()周围的全部内存屏障是否会使它成为可靠的计数器?

[英]Will full memory barriers around std::shared_ptr's use_count() make it a reliable counter?

我正在实现一个线程安全的“惰性同步”集,它是由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最初是一个简单的布尔值,而LazySet.counter不存在。 选择它们为shared_ptrs是为了能够原子地修改节点数上的计数器和节点上的延迟删除标记。 为了使is_empty()可以通过contains()线性化,必须在remove()同时修改两者。 (没有双倍宽CAS或其他内容,它不能是单独的bool标记和int计数器。)我希望使用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第2776期说:

如果不添加更多的防护,我们就无法使use_count()可靠。

轻松的内存顺序不是这里的问题。 use_count不是“可靠的”,因为在返回值之前,该值可能已更改 获取值本身并没有引起数据争夺,但是也没有阻止该值在基于该值的条件语句之前被修改的任何措施。

因此,您不能依靠它的值仍然有意义来对其进行任何操作(例外是,如果您仍持有shared_ptr实例,则使用计数不会变为0)。 使其可靠的唯一方法是防止对其进行更改。 因此,您需要一个互斥锁。

而且该互斥锁不仅要锁定use_count调用和用法,而且还必须在每次您从其中获取use_count的这些shared_ptr之一use_count

// ## SOME FULL MEMORY BARRIER
if (counter.use_count() == 1) {
    // ## SOME FULL MEMORY BARRIER

之前使用获取隔离墙,可以确保可以“看到”其他线程中所有所有者的所有重置结果(包括在分配和销毁过程中)。 获取围栏为所有随后的轻松操作提供了获取语义,从而防止它们“将来获取值”(无论如何,这是语义上的混乱,可能使所有程序正式为UB)。

(通话后,您不能放置任何有意义的栅栏。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM