带有危险指针的无锁存储器回收

时间:2014-08-08 13:20:05

标签: multithreading algorithm memory-management lock-free

Hazard pointers是一种在没有垃圾收集的情况下以无锁代码安全回收内存的技术。

这个想法是,在访问可以同时删除的对象之前,线程将其危险指针设置为指向该对象。想要删除对象的线程将首先检查是否有任何危险指针设置为指向该对象。如果是这样,删除将被推迟,以便访问线程不会最终读取已删除的数据。

现在,假设我们的删除线程开始迭代危险指针列表,并在i+1元素处被抢占。现在,另一个线程将危险指针设置为i到删除线程当前正在尝试删除的对象。然后,删除线程恢复,检查列表的其余部分,并删除对象,即使现在位于i的危险指针指向对象。

所以很明显只是设置危险指针是不够的,因为删除线程可能已经检查了我们的危险指针并确定我们的线程不想访问该对象。在设置危险指针后,如何确保我尝试访问的对象不会从我手中删除?

2 个答案:

答案 0 :(得分:14)

权威答案

original paper by Maged M. Michael对使用危险指针的算法施加了这一重要限制:

  

该方法需要无锁算法来保证不行   线程可以在可能被删除时访问动态节点   从该对象,除非至少有一个线程的相关危险   指针从一开始就一直指向那个节点   保证节点可以从对象的根中访问。该   方法可以防止任何退休节点的连续释放   由一个或多个线程的一个或多个危险指针指向   在移除之前的一个点。

删除线程的含义

正如Anton's answer中所指出的,删除是一个两阶段操作:首先,您必须“取消发布”该节点,将其从数据结构中删除,以便无法再从公共接口访问它。

此时,用迈克尔的术语来说,节点可能被移除。并发线程访问它不再安全(除非它们已经在整个过程中持有危险指针)。

因此,一旦删除了一个节点,删除线程就可以安全地迭代危险指针列表。即使删除线程被抢占,并发线程也可能不再访问该节点。在验证没有为节点设置危险指针后,删除线程可以安全地进入第二阶段的删除:实际的解除分配。

总之,删除线程的操作顺序是

D-1. Remove the node from the data structure.
D-2. Iterate the list of hazard pointers.
D-3. If no hazards were found, delete the node.

真正的算法稍微复杂一些,因为我们需要维护那些无法回收的节点列表,并确保它们最终被删除。这里已经省略了,因为它与解释问题中提出的问题无关。

访问线程的含义

设置危险指针不足以保证对其的安全访问。毕竟,在我们设置危险指针时,可能会删除该节点。

确保安全访问的唯一方法是,我们可以保证我们的危险指针一直指向该节点,从保证节点可以从对象的根目录开始。

由于代码应该是无锁的,因此只有一种方法可以实现:我们乐观地将危险指针设置为节点,然后检查该节点是否已被标记为可能被删除(即,它是不再可以从公共根目录之后

因此访问线程的操作顺序是

A-1. Obtain a pointer to the node by traversing the data structure.
A-2. Set the hazard pointer to point to the node.
A-3. Check that the node is still part of the data structure.
     That is, it has not been possibly removed in the meantime.
A-4. If the node is still valid, access it.

影响删除线程的潜在竞赛

可能删除节点(D-1)后,删除线程可能会被抢占。因此,并发线程仍然可以乐观地设置它们的危险指针(即使它们不被允许访问它)(A-2)。

因此,删除线程可能检测到虚假危险,阻止它立即删除节点,即使其他线程都不会再访问该节点。这将简单地延迟删除节点,就像合法的危险一样。

重要的是,该节点最终仍会被删除。

影响访问线程的潜在竞赛

在验证节点尚未被删除(A-3)之前,访问线程可能会被删除线程抢占。在这种情况下,不再允许访问该对象。

请注意,如果抢占发生在A-2之后,访问线程访问节点甚至是安全的(因为危险指针始终指向节点),但是因为它不可能访问线程来区分这种情况,它必须虚假地失败。

重要的一点是,如果节点尚未被删除,则只能访问该节点。

答案 1 :(得分:4)

  

想要删除对象的线程将首先检查是否有任何危险指针设置为指向该对象。

这是问题所在。 '删除'实际上是两阶段操作:

  1. 从容器或任何其他公共结构中删除。一般来说,取消发布它。
  2. 释放内存
  3. 因此,通过危险指针的迭代必须在它们之间进行,以防止您描述的情况:

      

    另一个线程将i处的危险指针设置为删除线程当前正在尝试删除的对象

    因为另一个线程无法获取被删除的对象。