meisel Asked: 2024-10-10 05:19:24 +0800 CST2024-10-10 05:19:24 +0800 CST 2024-10-10 05:19:24 +0800 CST 为什么我的 C++ stdlib 的 shared_ptr 在减少原子引用计数时会使用 acq_rel 排序? 772 对于我的机器的 C++ 标准库,它使用宽松排序来增加 shared_ptr 控制块中的引用计数,但使用 acq_rel 排序来减少引用计数。为什么要这样做?为什么不在增加时使用释放排序,在减少时使用获取排序? c++ 1 个回答 Voted Best Answer mpoeter 2024-10-10T21:00:35+08:002024-10-10T21:00:35+08:00 与普遍看法相反,std::shared_ptr它实际上并不是线程安全的。 中唯一shared_ptr线程安全的部分是带有引用计数器的控制块。但是,对象shared_ptr本身并不是线程安全的。因此,为了获得对共享对象的新引用,您必须获取现有 的副本shared_ptr,这意味着您已经拥有安全引用。如果实例shared_ptr本身被多个线程共享和访问,则必须使用其他方式来同步对它的访问,例如,通过使用atomic_load和 ,atomic_store它们通常在内部使用互斥锁。因此,ref-cnt 增量可以放宽,因为它不需要建立任何顺序。 关于析构函数中的递减,考虑一下我们到底需要按什么顺序进行是有意义的。shared_ptr允许多个线程对同一个共享对象进行安全引用。您有责任确保对该对象的任何并发操作都得到正确同步(如有必要),例如,如果您修改了对象的状态。这完全超出了 的范围shared_ptr。但是,shared_ptr一旦删除了对对象的最后一个引用, 就会负责销毁该对象。但为了正确销毁对象,我们必须确保我们对该对象有一个最新的视图。或者换句话说,对析构函数的调用发生在任何先前的修改之后。 当线程删除shared_ptr不是最后一个引用(即,在递减之后 ref-cnt 不为零)时,就没有什么可做的了 - 对象不必被销毁。我们唯一需要确保的是,我们对对象所做的任何修改都可以被将要销毁对象的线程看到。这是通过使用memory_order_release递减操作来实现的。 当一个线程删除最后一个引用(即,在递减之后 ref-cnt 为零)时,该线程将负责销毁该对象。为了建立所需的先发生顺序,我们需要它与其他线程的释放递减同步,即我们需要一些获取操作。因此,实际上最后一个线程必须使用,memory_order_acquire而所有其他线程都必须使用memory_order_release。 但是我们不知道它是否是最后一个线程 - 我们只能在减量之后确定这一点。因此,最简单的解决方案就是只使用memory_order_acq_rel- 这样,每个减量都会与任何先前的减量同步。但理论上,也可以只使用memory_order_release减量,load(std::memory_order_acquire)一旦我们意识到这是最后一个引用,就可以执行额外的或获取隔离。
与普遍看法相反,
std::shared_ptr
它实际上并不是线程安全的。 中唯一shared_ptr
线程安全的部分是带有引用计数器的控制块。但是,对象shared_ptr
本身并不是线程安全的。因此,为了获得对共享对象的新引用,您必须获取现有 的副本shared_ptr
,这意味着您已经拥有安全引用。如果实例shared_ptr
本身被多个线程共享和访问,则必须使用其他方式来同步对它的访问,例如,通过使用atomic_load
和 ,atomic_store
它们通常在内部使用互斥锁。因此,ref-cnt 增量可以放宽,因为它不需要建立任何顺序。关于析构函数中的递减,考虑一下我们到底需要按什么顺序进行是有意义的。
shared_ptr
允许多个线程对同一个共享对象进行安全引用。您有责任确保对该对象的任何并发操作都得到正确同步(如有必要),例如,如果您修改了对象的状态。这完全超出了 的范围shared_ptr
。但是,shared_ptr
一旦删除了对对象的最后一个引用, 就会负责销毁该对象。但为了正确销毁对象,我们必须确保我们对该对象有一个最新的视图。或者换句话说,对析构函数的调用发生在任何先前的修改之后。当线程删除
shared_ptr
不是最后一个引用(即,在递减之后 ref-cnt 不为零)时,就没有什么可做的了 - 对象不必被销毁。我们唯一需要确保的是,我们对对象所做的任何修改都可以被将要销毁对象的线程看到。这是通过使用memory_order_release
递减操作来实现的。当一个线程删除最后一个引用(即,在递减之后 ref-cnt 为零)时,该线程将负责销毁该对象。为了建立所需的先发生顺序,我们需要它与其他线程的释放递减同步,即我们需要一些获取操作。因此,实际上最后一个线程必须使用,
memory_order_acquire
而所有其他线程都必须使用memory_order_release
。但是我们不知道它是否是最后一个线程 - 我们只能在减量之后确定这一点。因此,最简单的解决方案就是只使用
memory_order_acq_rel
- 这样,每个减量都会与任何先前的减量同步。但理论上,也可以只使用memory_order_release
减量,load(std::memory_order_acquire)
一旦我们意识到这是最后一个引用,就可以执行额外的或获取隔离。