STL 内部:原子 shared_ptr

Talk big database, solutions, and innovations for businesses.
Post Reply
jrineakter
Posts: 888
Joined: Thu Jan 02, 2025 7:06 am

STL 内部:原子 shared_ptr

Post by jrineakter »

如何为脚本语言注册文件类型,以便用户在运行不受信任的脚本时收到警告?

针对某些类别的 IFNDR 的常见解决方案:让链接器验证相同的功能
陈瑞文
陈瑞文
C++20 标准引入了对共享指针的专门化:。它是如何工作的?std::atomicstd::atomic<shared_ptr<T>>

回想一下,正常情况由两个指针组成:一个调用时返回shared_ptr的存储指针和一个指向控制块的指针,该控制块包含强引用计数、弱引用计数和指向管理对象的指针。shared_ptrget()

原子版本shared_ptr具有相同的布局,但有一个变化:指向控制块的指针的底部两位用作标志。

练习:为什么使用控制块指针而不是存储指针来存储标志?

glibc++ libstdc++ 和 msvc 实现均使 合作伙伴电子邮件列表 用控制块指针的底部位作为锁定位:在对原子共享指针执行操作之前,实现会原子地设置锁定位以指示正在进行原子操作。如果有人尝试设置锁定位并发现它已设置,他们会等待位清除。当锁定位的所有者完成原子操作时,它会清除锁定位,从而允许任何等待的线程继续进行。

libstdc++ 和 msvc 之间的区别在于它们如何等待锁位清除。

libstdc++ 实现将锁定位视为自旋锁。如果设置了该位,它就会进入循环检查该位,直到最终清除为止。

msvc 实现使用指针的倒数第二位作为解锁通知位。如果设置了锁定位,msvc 将设置解锁通知位,然后调用wait()以等待通知。当清除锁定位时,msvc 还会清除解锁通知位,如果之前设置了解锁通知位,它会调用notify_all()以唤醒所有等待者。这会唤醒锁定线程,以便它可以尝试锁定现在已解锁的共享指针。(这也会唤醒任何调用过的应用程序线程wait(),但wait()会在内部重新检查条件,如果唤醒是虚假的,则会返回睡眠状态。)

对于wait()和notify_one()/ notify_all(),libstdc++ 和 msvc 都使用等待值改变的技术。msvc 实现使用Wait­On­Addressif available;否则,它会回退到由条件变量构建的手动管理版本。(条件变量从 Windows Vista 开始可用。支持 Windows XP 的最后一个 msvc 版本是 Visual Studio 2017。)libstdc++ 实现也使用手动管理版本,由 futexes if available 构建,否则条件变量。

因此,原子共享指针基本与普通共享指针相同,只是在控制块指针内隐藏了一个锁。

附加阅读:在不同的s之间进行转换时意味着什么shared_ptr。幻像和放纵共享指针。

额外观看:无锁的 std::atomic<std::shared_ptr> (视频)。无锁实现的演示从 27:50 开始。

附加说明:由于原子共享指针对于所有操作都是锁定的,因此您可以将其视为具有内置功能。因此,您可以在读取和写入操作上获得完全序列化。std::mutex

但是如果您对的使用主要是读取,很少写入,那么使用可能会获得更好的性能,因为允许多个所有者处于读取(共享)模式,这允许多个线程同时复制,而不是让它们相互等待。shared_ptrshared_mutexshared_mutexshared_ptr

额外奖励闲聊:内部锁的存在意味着如果一个线程在持有锁时被取消调度,所有其他线程都无法取得进展。而且 gcc 使用自旋锁而不是阻塞等待,这使其容易受到优先级反转死锁的影响:如果拥有自旋锁的线程的运行优先级低于正在旋转等待锁的线程,则优先级较高的旋转线程将消耗所有 CPU,等待优先级较低的线程释放锁。但优先级较低的线程无法释放锁,因为它被优先级较高的旋转线程抢占了 CPU。
Post Reply