【问题标题】:Tree structures and threads树结构和线程
【发布时间】:2011-01-31 08:29:07
【问题描述】:

我有一个速度关键的多线程程序,它涉及树结构中的数据。实现如下:

typedef struct
{
    // data pertaining to linkages, defining the architecture of the tree
    int parent_node;
    int child_node[MAX_CHILD_NODES];
    int number_of_children;

    // data pertaining to info at each node
    float interesting_info_A;
    char interesting_info_B[STRING_LEN];
    long interesting_info_C;
}
node_type;

node_type node[MAX_NUMBER_OF_NODES];

CRITICAL_SECTION node_critsec[MAX_NUMBER_OF_NODES];

程序进入和离开由 node_critsec[] 控制的临界区。因此,当我需要处理节点 n 的interesting_info_A/B/C 时,我进入该节点的临界区(node_critsec[n]),进行处理然后离开。该程序在树周围蜿蜒曲折,沿着与父母和孩子的链接走各种复杂的路径。该程序还将增长树,即添加新节点并相应地修改其他节点的父/子链接(树永远不会缩小)。我尝试确保每个线程一次只锁定一个节点,以避免死锁的风险。但是我有一个问题——如果我要添加一个新节点,那么我可能想要锁定节点的父节点,以便我可以调整它的子节点列表。在没有死锁或两个线程尝试修改同一节点中的数据的情况下让所有这些工作都变得有点像一场噩梦。关于何时以及应该遵循哪些节点来锁定/解锁我应该遵循一些指导原则 - 还是我必须非常聪明并计算出可能发生的事件的每一个排列?

【问题讨论】:

  • 你能解释一下你遇到死锁的地方吗?这意味着要么你的图表中有循环(不是树),要么在遍历期间你没有在获取子锁之前放弃父锁,这很容易纠正。
  • 据我了解,有些算法会自下而上,而其他算法会自上而下,依此类推。我认为他的意思是他需要在将新节点添加到父节点列表之前将其锁定到新节点,否则另一个线程可能会窃取新节点或出错(状态不一致:parent_node 设置为未列出此节点的节点节点作为子节点)。我不知道这个数据结构是否是最优的,但他认为与这个数据结构同步的需要是正确的。
  • @Tyler McHenry:我现在实际上并没有陷入僵局,尽管我过去曾遇到过。我得到它们的时间总是归结为同时锁定两个不同节点的线程之一。我现在的问题是两个不同的线程正在处理相同的节点数据。

标签: c multithreading data-structures deadlock


【解决方案1】:

一个简单的规则:为了避免在锁定多个项目时出现死锁,请始终以相同的顺序锁定它们。因此,如果您有项目 A、B、C 和 D,则应始终将它们锁定在字母顺序中,而不是其他顺序。如果您锁定了 C 并决定需要 B,那么您必须释放 C,然后锁定 B 并重新锁定 C。

我想,在一棵树上,你总是可以从顶部向下锁定。如果您需要锁定父级,则根据需要释放并重新获取锁定。

还有其他方案同样有效,但这个很简单。

编辑:你可以阅读一下here

【讨论】:

  • 假设它是正确的,我喜欢它的声音。漂亮而简单。
  • 我认为这是一个正确的方案。我特别喜欢从上到下锁定树,如果要锁定节​​点的父节点,请释放当前节点锁,移动到父节点,然后按顺序锁定对。
【解决方案2】:

如果增长树相对不常见,一种可能性是使用允许多个读取器但只允许一个写入器的读/写锁。在遍历期间使用单个 R/W 锁来锁定树本身(获取读锁)。任何数量的线程都可以做到这一点。当一个线程需要添加一个新节点时,它会获取写锁。这将在更新期间阻止所有读者。请注意,您可能需要设置读/写锁以给予写入者优先权以避免让他们挨饿。

使用该机制将消除单个线程获取单个节点的多个关键部分的需要,从而简化流程。

【讨论】:

  • 不幸的是,长树很常见。
  • @Mick:新节点是如何添加到父节点的?它们是否按特定顺序添加(例如,b-tree 样式)?还是简单地将子节点添加到现有节点的末尾(位置 number_of_children)?
  • 不幸的是,该机制比我问题中的简化代码所揭示的更复杂......而且我必须解释太多事情才能正确回答您的问题。但是 clintp 的回答可能很好地解决了我所有的问题。
【解决方案3】:

这让人想起文件系统中的节点锁定。如果您正在寻找参考资料,您可以查看 linux、BSD、open solaris 中的 VFS 层......但是,请注意它可能是复杂的东西,因此可能不是最好的参考示例。

我想在 clintp 提出的观点之外补充一些观点(注意他的观点)。

  1. 在需要时使用互斥锁锁定对整个树的访问并在完成后解锁它可能是值得的。尽管此关键部分中的单个线程为应用程序提供了线程,但它对于快速、安全地启动和运行非常有用。谁知道,它提供的性能可能已经足够好了。如果不是,至少它会让你前进。如果您使用读写信号量代替互斥锁,则可以稍微缓解瓶颈;一切都变成单线程的写入,而读取可以是并发的。

  2. 列出您希望对树进行的所有操作并对其进行分类(读取、写入、更新、移动、重命名、删除……)并确定您想要的并发度.从您所写的内容来看,您需要的不仅仅是只读的。您是否希望线程 A 能够读取未被线程 B 锁定以写入的节点?我根据经验说,跳过这一步会花费你很多时间。

希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多