`unique_ptr`的原子操作

J. *_*Doe 11 c++ multithreading smart-pointers atomic thread-safety

std::shared_ptr atomic_compare_exchange_weak和家人一样的原子操作的专业,但我找不到有关等效专业的文档std::unique_ptr.有吗?如果没有,为什么不呢?

Nat*_*ica 8

没有标准的原子功能std::unique_ptr.

我确实在Herb Sutter的Atomic Smart Pointers(N4058)中找到了一个论据

劳伦斯·克劳尔回应说:

shared_ptr锁定的原因之一是避免我们削弱原子模板参数的前提条件这是微不足道的情况,因此没有死锁的风险.

也就是说,我们可以削弱需求,以便参数类型只需要是lockfree,或者可能只是非递归锁定.

然而,尽管微不足道的事情具有合理的可测性特征,但我认为没有有效的机制来测试较弱的特性.

该提议已分配给并发子组,并且尚未处置.您可以在JTC1/SC22/WG21 - Papers 2014 mailing2014-07 查看状态

  • 该引用给出了为什么(pre c ++ 17)`std :: atomic <T>`不适用于智能指针的原因,但不是为什么std :: unique_ptr没有例如`atomic_compare_exchange_weak`的重载,而在那里适用于std :: shared_ptr (4认同)

Mat*_* M. 8

可以提供原子实例std::shared_ptr并且不可能这样做的std::unique_ptr原因在其签名中暗示.相比:

  • std::shared_ptr<T> VS
  • std::unique_ptr<T, D>DDeleter的类型在哪里.

std::shared_ptr 需要分配一个控制块,其中保持强弱计数,因此删除器的类型擦除是以微不足道的成本(一个简单的稍大的控制块).

因此,布局std::shared_ptr<T>通常类似于:

template <typename T>
struct shared_ptr {
    T* _M_ptr;
    SomeCounterClass<T>* _M_counters;
};
Run Code Online (Sandbox Code Playgroud)

并且可以原子地执行这两个指针的交换.


std::unique_ptr有一个零开销政策; std::unique_ptr与使用原始指针相比,使用a 不应产生任何开销.

因此,布局std::unique_ptr<T, D>通常类似于:

template <typename T, typename D = default_delete<T>>
struct unique_ptr {
    tuple<T*, D> _M_t;
};
Run Code Online (Sandbox Code Playgroud)

其中tuple使用EBO(空基优化)使得每当D为零大小时sizeof(unique_ptr<T>) == sizeof(T*).

但是,在D不是零大小的情况下,实现归结为:

template <typename T, typename D = default_delete<T>>
struct unique_ptr {
    T* _M_ptr;
    D _M_del;
};
Run Code Online (Sandbox Code Playgroud)

D是踢球者; 一般来说,不可能保证D可以在不依赖互斥体的情况下以原子方式进行交换.

因此,不可能std::atomic_compare_exchange*为通用提供一套专门的例程std::unique_ptr<T, D>.

请注意,该标准甚至不保证sizeof(unique_ptr<T>) == sizeof(T*)AFAIK,尽管它是一种常见的优化.


rus*_*tyx 7

要小心,unique_ptr即使指针本身是原子的,在线程之间共享可修改的内容也很少有意义.如果其内容发生变化,其他线程如何知道呢?他们不能.

考虑这个例子:

unique_ptr<MyObject> p(new MyObject);

// Thread A
auto ptr = p.get();
if (ptr) {
    ptr->do_something();
}

// Thread B
p.reset();
Run Code Online (Sandbox Code Playgroud)

线程A如何在调用后避免使用悬空指针p.get()

如果要在线程之间共享对象,请使用shared_ptr具有完全针对此目的的引用计数.


如果你真的想要它,你可以随时自己动手atomic_unique_ptr,简而言之:

#pragma once
#include <atomic>
#include <memory>

template<class T>
class atomic_unique_ptr
{
  using pointer = T *;
  std::atomic<pointer> ptr;
public:
  constexpr atomic_unique_ptr() noexcept : ptr() {}
  explicit atomic_unique_ptr(pointer p) noexcept : ptr(p) {}
  atomic_unique_ptr(atomic_unique_ptr&& p) noexcept : ptr(p.release()) {}
  atomic_unique_ptr& operator=(atomic_unique_ptr&& p) noexcept { reset(p.release()); return *this; }
  atomic_unique_ptr(std::unique_ptr<T>&& p) noexcept : ptr(p.release()) {}
  atomic_unique_ptr& operator=(std::unique_ptr<T>&& p) noexcept { reset(p.release()); return *this; }

  void reset(pointer p = pointer()) { auto old = ptr.exchange(p); if (old) delete old; }
  operator pointer() const { return ptr; }
  pointer operator->() const { return ptr; }
  pointer get() const { return ptr; }
  explicit operator bool() const { return ptr != pointer(); }
  pointer release() { return ptr.exchange(pointer()); }
  ~atomic_unique_ptr() { reset(); }
};

template<class T>
class atomic_unique_ptr<T[]> // for array types
{
  using pointer = T *;
  std::atomic<pointer> ptr;
public:
  constexpr atomic_unique_ptr() noexcept : ptr() {}
  explicit atomic_unique_ptr(pointer p) noexcept : ptr(p) {}
  atomic_unique_ptr(atomic_unique_ptr&& p) noexcept : ptr(p.release()) {}
  atomic_unique_ptr& operator=(atomic_unique_ptr&& p) noexcept { reset(p.release()); return *this; }
  atomic_unique_ptr(std::unique_ptr<T>&& p) noexcept : ptr(p.release()) {}
  atomic_unique_ptr& operator=(std::unique_ptr<T>&& p) noexcept { reset(p.release()); return *this; }

  void reset(pointer p = pointer()) { auto old = ptr.exchange(p); if (old) delete[] old; }
  operator pointer() const { return ptr; }
  pointer operator->() const { return ptr; }
  pointer get() const { return ptr; }
  explicit operator bool() const { return ptr != pointer(); }
  pointer release() { return ptr.exchange(pointer()); }
  ~atomic_unique_ptr() { reset(); }
};
Run Code Online (Sandbox Code Playgroud)

注意:此帖中提供的代码现已发布到公共领域.

  • 它实际上是非常有意义的:它实际上是一个常见的无锁模式,用于在堆上创建一个对象 - 一旦完成它 - 在共享变量中存储指向它的指针.现在,如果任何线程想要访问该对象,它首先必须原子地将该指针与nullptr交换,并在该线程完成后将指针存回.这样你就可以确保只有一个线程可以同时访问一个对象. (9认同)
  • 例如,因为一个人是生产者,一个人是消费者(或者您甚至有多个消费者)。然后,您想将所有权转让给获得指针的人。无论如何:假设您具有原子的unique_ptr,那么在您将生命周期与可见性/可访问性分开处理时,我看不到它如何简化事情。 (2认同)