如果这是指向所管理资源的最后一个smart_ptr,我们知道下面的析构函数代码应该释放控制块。在下面的“ if”和“ delete”之间是否存在赛车问题?如果我们尝试在“ if”之后和“ delete”之前在另一个线程中创建一个全新的smart_ptr obj,该怎么办?
// Thread D:
// smart_ptr destructor
~smart_ptr() {
if (control_block_ptr->refs.fetch_sub(1, memory_order_acq_rel) == 0) {
delete control_block_ptr;
}
}
答案 0 :(得分:2)
首先,进行更正:
control_block_ptr->refs.fetch_sub(1, memory_order_acq_rel) == 0
您的意思是== 1
。 fetch_sub
返回减去前 而不是之后的值。
已清除:
在下面的“ if”和“ delete”之间是否存在赛车问题?如果我们尝试在“ if”之后和“ delete”之前在另一个线程中创建一个全新的smart_ptr obj,该怎么办?
因此,您正在破坏smart_ptr
对象。如果control_block_ptr->refs
为1,则表示当前smart_ptr
是拥有控制块的 only对象,对吗?那你在担心什么呢?
毕竟,某些“全新smart_ptr
”将具有自己的“全新” control_block_ptr
和其引用计数。那绝不会干扰被摧毁的那一个。
唯一可能出现问题的方法是在smart_ptr
的销毁与复制之间的竞赛。所谓“它”,是指同一对象;不仅是任何smart_ptr
,而且是该销毁的控制块的唯一所有者。
请参见,如果您有两个smart_ptr
对象共享相同的状态,则复制一个对象同时销毁另一个对象就可以了,因为无论这些操作的顺序如何,一切都可以进行。其中之一将减少参考计数。另一个会增加它。但是因为引用计数从2开始,所以引用计数永远不会达到0。
但是,如果要复制的对象是要销毁的对象,那么……那只是断的代码。请注意,尽管std::shared_ptr
特别声明在共享状态下对引用计数的更新是原子的,并且不会引起数据争用,但是从不同线程对同一shared_ptr
对象的多次访问却是数据争用(因此未定义的行为)。只要访问中的至少一个不是const
操作;该副本是一个const
操作,但 description 不是,因此适用。
smart_ptr
也必须如此:如果您尝试复制要销毁的同一对象,那么会发生坏事。
答案 1 :(得分:1)
通常(至少对于Boost和标准库智能指针而言),智能指针对象本身并非设计为线程安全的。他们指向的对象的管理/生命周期只有线程安全的。在多个线程中同时使用smart_ptr对象本身并不安全,但同时引用多个具有相同基础数据的smart_ptr对象是安全的。
在您的示例中,必须从其他现有的smart_ptr中复制在不同线程中创建的“全新smart_ptr”。
如果现有的smart_ptr不是要销毁的对象,它将确保不会if
分支被销毁,因为它将使对象保持活动状态。
如果现有的smart_ptr是示例中要销毁的对象,那么您会遇到问题,但这是因为您正尝试使用正在销毁过程中的smart_ptr对象。即使在这种情况下析构函数没有竞争,另一个线程仍然有可能在被销毁后继续使用smart_ptr,这在C ++中始终是非法的。
答案 2 :(得分:1)
简单地说,不。
一种竞争条件,即取决于特定操作顺序的最终结果,仅在引用计数上才有可能,一个操作可以使一个对象从一个不包含计数的对象成为个计数/ em>,也就是说,当您的参考文献薄弱时。
这里有一个竞赛端点是因为引用计数(RC)达到零而是否释放了资源; “哪个线程执行的确切操作使RC为零”这个问题是一个有趣的端点:在多线程上下文中使用RC管理资源时的隐含假设是,任何线程(拥有最后一个所有者)都可以释放资源。
根据定义,RC是每个所有者对RC的严格贡献的总和(由于RC是所有者的数量,所以恰好为1,但这并不是很重要)。在抽象的情况下,也可以将RC形式化为所有者的集合,并且由于RC的特殊性,整数将有效表示所需信息:
因此,您可以将数字想象成一个所有者列表,每个孩子都用一条垂直线表示孩子们学习数字时(3 = {|||
),并且只有单个所有者知道自己的酒吧(您可以要么说所有的条都一样,要么它们有不同的颜色)。 (这些整数显然用二进制表示。)
在仅所有者存在的设置中(没有操作可以通过“弱引用”引用RC),所有者集中只有两个基本操作:
重复项只会添加一个竖线。在图形显示中,您甚至可以通过擦除中间的条来分割条,并以两个半条结尾。这就是所有权被解散(就像您出售了一家贸易公司的部分股份一样)。
删除操作会删除属于所有者的垂直条(当然,实际上没有识别出的条,根本没有条,并且它以二进制表示的整数的减量操作); 如果该操作删除了该最后一个栏,则删除线程负责释放资源。
您可以轻松地看到零RC对应于一组空条,这在所有所有者放弃所有权后发生。有一个竞争条件可以确定在任何给定时间的准确小节数,但这很重要:每个线程中的每个所有者都知道自己是所有者,这很重要。对其他所有者的数量本质上是无所谓。 (如果您没无动于衷,那么您可能首先希望拥有唯一所有权,以便能够更改资源而不影响其他用户。)
在集合中存在竞争条件意味着必须使用内部原子(或类似的替代物),但总体上所有者不应该在意。如果在表示中意外删除了某个特定的“条”,那将是灾难性的,这意味着所有者没有被考虑,并且将是僵尸所有者:它将认为它拥有资源,但实际上不拥有任何东西。 RMW原子操作(读修改写)保证不会发生:任何仅通过RMW操作修改的原子对象都不会丢失修改。
非常所有权的概念意味着它不能一无是处创建:您只能成为某物的所有者:
那只是常识。
由于所有权的破坏是不可逆的,因此达到零所有者是一个终止事件;在这一点上,保证RC再也不会改变,甚至无法再次测量。 (因此,RC表示的所有权可与资源本身的所有权相叠加。)
这些属性使真正所有权的RC实现非常简单。当弱引用(即仅用于RC的 watcher )进入图片时,情况会有所不同:RC测量工具可保证它可以在将来的任何时候对RC进行测量,无论是不管用户资源是否仍然存在,托管用户资源是否仍然具有所有者。对于弱引用,可以通过原子操作将RC读取为零,即在所有者集合的图形表示中没有竖线。这意味着RC的生存期变得不同于用户资源的生存期:RC本身(通过内部RC)成为另一个托管资源。
引用数量少,就可以创建所有者而不共享现有所有权:引用数量不足是对未来所有权的“选择”(不可靠)。尽管只能从真实(即强)引用中创建弱引用,但这与所有权的一般原则相冲突。
因此,只有 具有弱引用, RC可以持久地为零,即在最后一次减少操作与RC释放之间的较小间隔之外为零。使用弱引用意味着必须将用户代码设计为处理RC可能为零的情况,并且在MT代码中,可能会在一个放弃作为最后所有者的所有权的线程与另一个试图从弱者重新创建所有权的线程之间进行竞争。参考。
在现实生活中,传统文化知识是免费的,可以任意复制。但是,对于很少有人知道的历史传统知识(例如家庭烹饪食谱),您最好在拥有该知识的最后一个人去世之前进行复制:死于传统知识的人与人之间会有一个 race 获取和传播该知识。这基本上与弱参考/强参考种族问题相同。
因此,弱引用可以用于模拟现实世界中的问题,在这种情况下,如果外部因素将观察者强行关闭,则观察者无法强行使资源保持活动状态,但是可以观察到资源的发展(存在时)。提倡对强项的弱引用会在转换完成的那一刻快照一下资源的活泼性,并且本质上是不道德的。
请注意,任何有意义地使用许多原语在某种程度上都是不道德的:您使用互斥锁,因为您不知道哪个线程首先需要对资源进行独占访问;如果您知道执行的确切顺序,则可以对线程进行序列化,并完全避免了线程的复杂性。争夺对资源的访问并不是错误。只有当程序执行的正确性取决于事件的特定顺序时,才会出现错误。
我们知道下面的析构函数代码应该释放控件 如果这是指向资源的最后一个smart_ptr,则阻止 管理。
是的,并且当RC的有效寿命达到零时(即实现了真正的所有权并且不支持弱引用时),这是正确的期望行为。对于Boost或标准shared_ptr
,它确实支持“弱”指针(也称为非拥有观察者),这不是。
尽管您在语义上提到的种族条件是不可能的,但这里有一些问题:
~smart_ptr() { if (control_block_ptr->refs.fetch_sub(1, memory_order_acq_rel) == 0) { delete control_block_ptr; } }
如所解释的,当不存在纯粹的观察者(不属于观察者并且可以看到零RC)时,RC的生存期与受管用户资源相同。我没有在此处看到用户资源的发布(可能在其他地方)。
用户资源本身在哪里管理?它在*control_block_ptr
对象的析构函数中吗?您可以再发布一些代码来获得完整的图片吗?
您还使用了 post 减量运算fetch_sub
而不是pre-decrement运算:“ post”运算返回前一个值 then 执行操作。 使用后期操作,您要操作的特殊有趣的RC值,即最后一个所有者停止成为所有者之前的值是1,而不是0 。
答案 3 :(得分:0)
如果构造函数不是越野车,那么它将看到refcount已经为零,因此销毁过程已经开始并且是不可逆的。因此,从其他线程的POV看,即使ref delete
尚未真正释放内存,当refcount达到零时该对象也已被破坏。
此外,如果析构函数将refcount设为零,则意味着不再再有shared_pointer
个对象可以从中进行复制构造。 (除非您对对象本身(而不是控制块)有一个“售后使用”的错误,在这种情况下您使用的是错误的:通常,您不会引用shared_ptr
对象。)>
因此,如果我正确地记住了shared_ptr的工作方式,那么这个问题就不存在了。 (因此,为什么可以将refcount保留在将被释放的对象中。。在非垃圾收集的环境中,回收问题很困难,因为您无法释放某些其他线程可能会释放的内存仍然有一个指针,请参阅用户空间与内核RCU的实现以获取挑战的示例无锁链表队列可能总是将节点返回到该类型对象的专用空闲列表,而不是 一个通用池,可以让它们作为其他东西重用或不与系统调用映射。)
但是在一般情况下,如果使用refcount = 0,则看到refcount = 0意味着销毁已经超过了不归路的地步,并且您尝试获取对其的新引用失败了。 (即发生在破坏之后,以原子计数器建立的全局顺序)。
当然,此“可见”将位于fetch_add(+1)
的返回值中。
无论如何,这种用于尝试获取新引用的设计使得在递减计数设为零后可以安全地进行重新分配。