47

交换两个unique_ptrs 不能保证是线程安全的。

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 中这样做的惯用方式是什么?

4

3 回答 3

24

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

std::unique_ptr没有锁你不能这样做。Evenstd::atomic<int>没有提供一种原子交换两个值的方法。您可以自动更新一个并取回其先前的值,但从概念上讲,交换是三个步骤,就std::atomicAPI 而言,它们是:

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

这是一个 atomic read后跟 atomic read-modify-write后跟 atomic write。每个步骤都可以原子地完成,但你不能在没有锁的情况下原子地完成这三个步骤。

对于不可复制的值,例如std::unique_ptr<T>您甚至不能使用上面的loadandstore操作,但必须这样做:

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因为它无法实现,因为正如 Stas 的回答所说,大多数处理器都不可能。C++ 标准没有将功能标准化的习惯,这在所有当代架构上都是不可能的。(反正不是故意的!)

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

atomic<T*> global;

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

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

于 2013-03-17T19:14:40.127 回答
23

两个指针的无锁交换

对于这个问题,似乎没有通用的无锁解决方案。为此,您需要能够将新值原子地写入两个非连续的内存位置。这称为DCAS,但在 Intel 处理器中不可用。

所有权无锁转移

这是可能的,因为它只需要以原子方式将新值保存到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. 保存localglobalifglobal仍然等于temp(它没有被其他线程更改)。如果这不是真的,请再试一次。

实际上,这有点矫枉过正,因为在更改旧值之前CAS,我们不会对旧值做任何特别的事情。global所以,我们就可以使用原子交换操作:

std::atomic<T*> global;

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

请参阅乔纳森的答案以获得更简短和优雅的解决方案。

无论如何,您将不得不编写自己的智能指针。您不能将此技巧与标准一起使用unique_ptr

于 2013-03-17T19:02:48.717 回答
0

这是一个有效的解决方案

您必须编写自己的智能指针

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(...)

于 2019-03-05T18:52:01.117 回答