【发布时间】:2016-01-28 17:09:02
【问题描述】:
std::shared_ptr has specializations for atomic operations 像 atomic_compare_exchange_weak 和家人,但我找不到关于 std::unique_ptr 的等效专业化的文档。有吗?如果没有,为什么不呢?
【问题讨论】:
标签: c++ multithreading thread-safety atomic smart-pointers
std::shared_ptr has specializations for atomic operations 像 atomic_compare_exchange_weak 和家人,但我找不到关于 std::unique_ptr 的等效专业化的文档。有吗?如果没有,为什么不呢?
【问题讨论】:
标签: c++ multithreading thread-safety atomic smart-pointers
可以提供std::shared_ptr 的原子实例而不能为std::unique_ptr 这样做的原因在他们的签名中有所暗示。比较:
std::shared_ptr<T> 与std::unique_ptr<T, D> 其中D 是Deleter 的类型。std::shared_ptr 需要分配一个控制块来保存强计数和弱计数,因此删除器的类型擦除成本微不足道(只是稍微大一点的控制块)。
因此std::shared_ptr<T>的布局大致类似于:
template <typename T>
struct shared_ptr {
T* _M_ptr;
SomeCounterClass<T>* _M_counters;
};
并且可以原子地执行这两个指针的交换。
std::unique_ptr 具有零开销策略;与使用原始指针相比,使用 std::unique_ptr 不会产生任何开销。
因此std::unique_ptr<T, D>的布局大致类似于:
template <typename T, typename D = default_delete<T>>
struct unique_ptr {
tuple<T*, D> _M_t;
};
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;
};
这个D 是这里的踢球者;一般来说,不依赖互斥体,不可能保证D 可以以原子方式交换。
因此,无法为通用 std::unique_ptr<T, D> 提供一套专门的 std::atomic_compare_exchange* 例程。
请注意,该标准甚至不保证 sizeof(unique_ptr<T>) == sizeof(T*) AFAIK,尽管它是一种常见的优化。
【讨论】:
is_lock_free == true 进行实例化,像任何其他太大而无法 CAS 对象一样。
没有std::unique_ptr 的标准原子函数。
我确实找到了为什么不在 Herb Sutter 的 Atomic Smart Pointers(N4058) 中的论据
Lawrence Crowl 回复添加:
shared_ptr 锁定的原因之一是它的方式是避免我们削弱原子模板参数的前提条件,即它是微不足道的,因此没有死锁的风险。
也就是说,我们可以削弱要求,使参数类型只需要是无锁的,或者可能只是非递归锁定。
然而,虽然微不足道的特征可以合理地测试,但我认为没有有效的机制来测试较弱的属性。
该提案已分配给并发子组,目前尚无处置。您可以通过JTC1/SC22/WG21 - Papers 2014mailing2014-07 查看状态
【讨论】:
std::atomic<T> 不适用于智能指针,但不能解释为什么没有重载例如atomic_compare_exchange_weak 用于 std::unique_ptr,而有用于 std::shared_ptr
小心,在线程之间共享可修改的unique_ptr 很少有意义,即使指针本身是原子的。如果它的内容发生了变化,其他线程怎么知道呢?他们不能。
考虑这个例子:
unique_ptr<MyObject> p(new MyObject);
// Thread A
auto ptr = p.get();
if (ptr) {
ptr->do_something();
}
// Thread B
p.reset();
线程 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(); }
};
注意:本文中提供的代码特此发布到公共领域。
【讨论】:
p->do_something_long()(没有额外的原始参考),则会出现同样的问题。然而,在这种假设下, shared_ptr 本身并没有帮助。使用时需要“全局”共享指针的本地副本。注意:do_something_long()只是为了突出问题。由于它是并发的,因此方法需要多长时间并不重要。