锁定免费双的unique_ptr的交换(Lock-free swap of two unique_p

2019-08-17 16:48发布

交换两个unique_ptr s没有保证是线程安全的。

std::unique_ptr<T> a, b;
std::swap(a, b); // not threadsafe

因为我需要原子指针交换,因为我喜欢的所有权处理unique_ptr ,有一个简单的方法来他们两个结合起来?


编辑:如果这是不可能的,我打开替代品。 我至少希望做这样的事情:

threadshared_unique_ptr<T> global;

void f() {
   threadlocal_unique_ptr<T> local(new T(...));
   local.swap_content(global); // atomically for global
}

什么是C ++ 11这样的惯用方法是什么?

Answer 1:

无锁两个指针的交换

似乎有对这个问题没有一般无锁解决方案。 要做到这一点,你需要一个可能性,原子写入新的值到两个非连续的内存位置。 这就是所谓的DCAS ,但它不具备的英特尔处理器。

无锁所有权转移

这一个是可能的,因为它是只需要保存原子新值global和接收其旧值。 我的第一个想法是使用CAS操作。 看看下面的代码来获得一个想法:

std::atomic<T*> global;

void f() {
   T* local = new T;
   T* temp = nullptr;
   do {
       temp = global;                                                   // 1
   } while(!std::atomic_compare_exchange_weak(&global, &temp, local));  // 2

   delete temp;
}

脚步

  1. 记住当前global的指针temp
  2. 保存localglobal ,如果global仍然等于temp (它没有被其他线程改变)。 再试一次,如果这是不正确的。

其实, CAS是矫枉过正那里,因为我们没有做什么特别的旧global被改变之前的值。 所以,我们正好可以利用原子交换操作:

std::atomic<T*> global;

void f() {
   T* local = new T;
   T* temp = std::atomic_exchange(&global, local);
   delete temp;
}

见乔纳森的回答甚至更短的和优雅的解决方案。

无论如何,你将不得不编写自己的智能指针。 您不能使用这一招用标准unique_ptr



Answer 2:

以原子修改两个变量的惯用方法是使用一个锁。

你不能做到这一点std::unique_ptr没有锁。 甚至std::atomic<int>不提供一种方式以原子交换两个值。 您可以更新一个原子,并得到其以前的值回来了,但交换是概念上的三个步骤,在方面std::atomic API,他们是:

auto tmp = a.load();
tmp = b.exchange(tmp);
a.store(tmp);

这是一个原子读取随后的原子读-修改-写操作后紧跟一个原子写入 。 每一步都可以自动完成的,但你不能没有一个锁的时候,所有的三个原子。

对于不可复制的价值,如std::unique_ptr<T>你甚至不能使用loadstore操作上面,但必须做到:

auto tmp = a.exchange(nullptr);
tmp = b.exchange(tmp);
a.exchange(tmp);

这三年读-修改-写操作。 (你真的不能使用std::atomic<std::unique_ptr<T>>要做到这一点,因为它需要一个平凡的,可复制的参数类型, std::unique_ptr<T>是不是任何一种可复制。 )

为了用更少的操作做到这一点就需要所不支持的不同API std::atomic ,因为它不能被实现,因为作为斯塔斯的回答说,这是不可能的大多数处理器。 C ++标准是不规范的功能,所有现代的架构是不可能的习惯。 (不是故意反正!)

编辑:您更新的问题询问有关一个非常不同的问题,在第二个例子中,你不需要一个原子交换,影响两个对象。 只有global的线程之间共享,所以你不关心,如果更新到local是原子,你只需要更新原子global和检索旧值。 规范C ++ 11做的方法是使用std:atomic<T*>和你甚至不需要第二可变:

atomic<T*> global;

void f() {
   delete global.exchange(new T(...));
}

这是一个读-修改-写操作。



Answer 3:

这是对一个有效的解决方案

你必须写自己的智能指针

template<typename T>
struct SmartAtomicPtr
{
    SmartAtomicPtr( T* newT )
    {
        update( newT );
    }
    ~SmartAtomicPtr()
    {
        update(nullptr);
    }
    void update( T* newT, std::memory_order ord = memory_order_seq_cst ) 
    {
        delete atomicTptr.exchange( newT, ord );
    }
    std::shared_ptr<T> get(std::memory_order ord = memory_order_seq_cst) 
    { 
        keepAlive.reset( atomicTptr.load(ord) );
        return keepAlive;
    }
private:
    std::atomic<T*> atomicTptr{nullptr};
    std::shared_ptr<T> keepAlive;
};

它是基于@Jonathan Wakely的末片段。

希望的是,这样的事情将是安全的:

/*audio thread*/ auto t = ptr->get() ); 
/*GUI thread*/ ptr->update( new T() );
/*audio thread*/ t->doSomething(); 

问题是,你可以做这样的事情:

/*audio thread*/ auto* t = ptr->get(); 
/*GUI thread*/ ptr->update( new T() );
/*audio thread*/ t->doSomething(); 

并没有什么让t活着的音频线时,GUI线程调用ptr->update(...)



文章来源: Lock-free swap of two unique_ptr