上次,我们了解了 Microsoft C++ 标准库如何实现和wait。notify_*std::atomic<std::shared_ptr<T>>今天,我们将了解(截至撰写本文时)实现的另一个库std::atomic<std::shared_ptr<T>>:libstdc++。
首先要注意的是,unix 上传统的“等待值改变”的机制是 futex,但是 futexes(futexen?)限制为 4 字节值,这对于 64 位指针来说是不够的,更不用说里面的两个指针了shared_ptr。
此时,我将介绍libstdc++ 如何实现对原子值的等待,特别是有关如何处理不适合的类型__platform_wait_t的部分。本讨论的其余部分将把它视为已解决的问题,并重点关注共享指针部分。
代码锁定shared_ptr(通过设置控制块指针的最低位,如我们前面所讨论的),然后检查存储的指针和控制块指针是否都匹配。如果不匹配,则等待已满足,我们释放锁定并返回。否则,我们要求完成等待。 _Atomic_count::_M_wait_unlock
顾名思义,_M_wait_unlock清除锁定位(从而解锁共享指针)然后等待值从当前值改变。
将通知转发给原子值。
所以归根结底,等待和通知原子共享指针归结为等待和通知其控制块指针。
但请稍等一下。语言规范规定,当存储指针或控制块指针发生变化时,对原子共享指针的等待就会得到满足。但此代码仅等待控制块指针发生变化。我们有错误吗?
让我们编写一个测试程序来看看我们的理 所有者/合伙人/股东电子邮件列表 论是否成立,或者是否有其他东西(比如 msvc 的指数退避)可以拯救我们。
此程序启动一个线程,等待一秒钟,让主线程有机会到达p.wait()。然后,它通过仅修改存储的指针并重用控制块来更改原子共享指针,然后通知主线程。如果程序在一秒钟后仍在运行,则等待未被唤醒,我们将终止该程序。
同时,启动信号线程后,主线程等待原子共享指针,当等待满足时,退出程序。
您希望此程序干净地退出。发信号线程修改了原子共享指针,从而满足了等待条件。(即使我们在修改原子共享指针之前没有休眠一秒钟,等待仍会得到满足,因为原子共享指针中的值不再匹配q。)
实际上,这个程序在 时崩溃了。std::terminate
因此,我们似乎在 libstdc++ 中发现了一个错误。(-dumpversion例如 14.2.0。)如果共享指针仅更改了其存储的指针而不更改其控制块,则等待原子共享指针不会通知。原子等待应该是 16 字节等待,涵盖存储指针和控制块指针。
附加闲聊:我发现一个有趣的现象,语言规范事后才为原子共享指针添加了等待/通知支持,几乎没有任何讨论或考虑,好像它太微不足道而不值得担心。而且三个主要实现中的两个搞砸了。(第三个主要实现,clang 的 libc++ 呢?哦,他们还没有实现它!)
我对这个话题很好奇,因为关于原子共享指针上的通知/等待,我首先想到的是“天哪,共享指针的大小是常规指针的两倍。我想知道这些实现是如何设法原子地等待大于寄存器的东西的?”当我深入研究这些实现时,我发现答案是“不正确”。