简体   繁体   English

原子交换两个std :: atomic <T*> 在C ++ 11中以无锁方式对象?

[英]Atomic exchange of two std::atomic<T*> objects in a lock-free manner in C++11?

The following code is a skeleton of an atomic pointer class taken from a simulated annealing application in the PARSEC benchmark suite for shared-memory multiprocessors . 以下代码是一个原子指针类的框架,它取自PARSEC基准套件中用于共享内存多处理器的模拟退火应用程序。

In that application, the central data structure is a graph (more specifically, a netlist of an integrated circuit). 在该应用中,中央数据结构是图(更具体地,集成电路的网表)。 Each node in the graph has an attribute indicating its physical location. 图中的每个节点都有一个指示其物理位置的属性。 The algorithm spawns many threads and each thread repeatedly and randomly selects two nodes and exchanges their physical locations if that results in better routing cost for the chip. 该算法产生许多线程并且每个线程重复并随机选择两个节点并交换它们的物理位置,如果这导致芯片的更好的路由成本。

Because the graph is huge and any pair of nodes can be chosen by each thread, the only workable solution is a lock-free concurrent data structure (CDS). 因为图形很大并且每个线程都可以选择任何一对节点,所以唯一可行的解​​决方案是无锁并发数据结构(CDS)。 That's why the following AtomicPtr class is crucial (it is used to atomically exchange pointers to two physical location objects in a lock-free manner). 这就是为什么以下AtomicPtr类是至关重要的(它用于以无锁方式原子地交换指向两个物理位置对象的指针)。 The function atomic_load_acq_ptr() is a defined in assembly code and corresponds closely to std::atomic<T*>::load(memory_order_acquire) . 函数atomic_load_acq_ptr()是在汇编代码中定义的,并且与std::atomic<T*>::load(memory_order_acquire)紧密对应。

I want to implement that CDS using C++11 atomics. 我想用C ++ 11原子实现该CDS。

template <typename T>
class AtomicPtr {
  private:
    typedef long unsigned int ATOMIC_TYPE;
    T *p __attribute__ ((aligned (8)));
    static const T *ATOMIC_NULL;
    inline T *Get() const {
        T *val;
        do {
            val = (T *)atomic_load_acq_ptr((ATOMIC_TYPE *)&p);
        } while(val == ATOMIC_NULL);
        return val;
    }
    inline void Swap(AtomicPtr<T> &X) {
        // Define partial order in which to acquire elements to prevent deadlocks
        AtomicPtr<T> *first;
        AtomicPtr<T> *last;
        // Always process elements from lower to higher memory addresses
        if (this < &X) {
            first = this;
            last  = &X;
        } else {
            first = &X;
            last  = this;
        }
        // Acquire and update elements in correct order
        T *valFirst = first->Checkout(); // This sets p to ATOMIC_NULL so all Get() calls will spin.
        T *valLast  =  last->PrivateSet(valFirst);
        first->Checkin(valLast); // This restores p to valLast
    }
};

The std::atomic<T*>::exchange() method can only be used to exchange a bare T* pointer with a std::atomic<T*> object. std::atomic<T*>::exchange()方法只能用于与std::atomic<T*>对象交换裸T*指针。 How to do exchange of two std::atomic<T*> objects in a lock-free manner? 如何以无锁方式交换两个std::atomic<T*>对象?

What I can think of is that the AtomicPtr class below can itself be based on std::atomic<T*> by declaring: 我能想到的是下面的AtomicPtr类本身可以基于std::atomic<T*>来声明:

std::atomic<T*> p;

and replacing all atomic_load_acq_ptr() calls by std::atomic<T*>::load(memory_order_acquire) and replacing all atomic_store_rel_ptr() calls by std::atomic<T*>::store(memory_order_release) . 和更换所有atomic_load_acq_ptr()通过调用std::atomic<T*>::load(memory_order_acquire)和替换所有atomic_store_rel_ptr()通过调用std::atomic<T*>::store(memory_order_release) But my first thought was that std::atomic<T*> should replace AtomicPtr itself and there may be a clever way to exchange std::atomic<T*> objects directly. 但我的第一个想法是std::atomic<T*>应该取代AtomicPtr本身,并且可能有一种聪明的方式直接交换std::atomic<T*>对象。 Any thoughts? 有什么想法吗?

It seems to me that the simpler way to get what you wish is to replicate the logic that you have seen here. 在我看来,获得你想要的更简单的方法是复制你在这里看到的逻辑。

The problem is that there is no possibility to get an atomic operations across two atomic objects, so you have to follow a procedure: 问题是不可能跨两个原子对象获取原子操作,因此您必须遵循以下过程:

  • order the atomics (to avoid dead-locks) 命令原子(避免死锁)
  • "lock" all but the last one (increasing order) “锁定”除了最后一个(增加顺序)
  • perform the operation atomically on the last one 在最后一个上以原子方式执行操作
  • perform the operation and "unlock" the others one at a time (decreasing order) 执行操作并一次“解锁”其他一个(递减顺序)

This is, of course, quite imperfect: 当然,这非常不完美:

  • not atomic: whilst you are busy locking a variable, any of the not yet locked could change state 不是原子的:当你忙着锁定变量时,任何尚未锁定的变量都可以改变状态
  • not obstruction free: if for some reason a thread is blocked whilst having locked a variable, all other pending threads are also blocked; 不受阻碍:如果由于某种原因在锁定变量时线程被阻塞,则所有其他未决线程也被阻止; be careful to avoid deadlocks here (should you have other locks) 小心避免死锁(如果你有其他锁)
  • brittle: a crash after locking a variable leaves you stranded, avoid operations that may throw and/or use RAII to "lock" 脆弱:锁定变量后崩溃使您陷入困境,避免可能抛出和/或使用RAII“锁定”的操作

However it should work relatively well in practice in the case of only 2 objects (and thus one to lock). 然而,在仅有2个物体(因此锁定一个物体)的情况下,它在实践中应该相对较好地工作。

Finally, I have two remarks: 最后,我有两个评论:

  • in order to lock you need to be able to define a sentinel value, 0x01 usually works well for pointers. 为了锁定你需要能够定义一个sentinel值, 0x01通常适用于指针。
  • the C++ Standard does not guarantee that std::atomic<T*> be lock-free, you can check this for your particular implementation and platform with std::atomic<T*>::is_lock_free() . C ++标准不保证std::atomic<T*>是无锁的,你可以使用std::atomic<T*>::is_lock_free()来检查你的特定实现和平台。

The closest you can come without a spinlock is: 没有螺旋锁的最接近的是:

std::atomic<T> a;
std::atomic<T> b;
a = b.exchange(a);

Which is thread safe for b . 哪个是b线程安全。

a may not be concurrently accessed. a可能无法同时访问。

Have you checked out a CAS (compare and swap ) operation? 你检查了CAS(比较和交换)操作吗?

   std::atomic<T*> v;

      while(!v.compare_exchange_weak(old_value,new_value, std::memory_order_release, memory_order_relaxed))

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

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