多线程链表遍历

时间:2012-09-25 21:02:15

标签: c++ list linked-list

给定(双重)链接的对象列表(C ++),我有一个我想要多线程的操作,以对每个对象执行。每个对象的操作成本不均匀。由于各种原因,链表是这组对象的首选存储。每个对象中的第一个元素是指向下一个对象的指针;第二个元素是列表中的上一个对象。

我通过构建节点数组并应用OpenMP解决了这个问题。这给了不错的表现。然后我切换到我自己的线程例程(基于Windows原语)并使用InterlockedIncrement()(作用于数组中的索引),我可以实现更高的整体CPU利用率和更快的吞吐量。从本质上讲,线程沿着元素“跳跃式”运行。

我的下一个优化方法是尝试消除创建/重用链表中的元素数组。但是,我想继续这种“跳跃式”方法,并以某种方式使用一些不存在的例程,可称为“InterlockedCompareDereference” - 以原子方式比较NULL(列表末尾)和有条件的解除引用&存储,返回解除引用的值。

我不认为InterlockedCompareExchangePointer()会起作用,因为我无法原子地取消引用指针并调用此Interlocked()方法。我做了一些阅读,其他人正在建议关键部分或自旋锁。关键部分在这里似乎很重要。我很想尝试旋转锁,但我想我首先在这里提出问题并询问其他人在做什么。我不相信InterlockedCompareExchangePointer()方法本身可以像旋转锁一样使用。然后还必须考虑获取/发布/围栏语义......

想法?谢谢!

4 个答案:

答案 0 :(得分:1)

关键部分的重量不是很重。假设他们可以快速获得锁,他们就像旋转锁一样。

问题的解决方案取决于您是否正在修改列表。如果你没有修改列表,你需要做的就是将InterlockedCompareExchange对象中的值初始化为0.交换值为1,如果你得到0,那么你做你的行动,如果你得到1回,你跳过下次在列表上执行操作时,您将在2中交换并检查1/2而不是0/1。

如果您要更改列表,那么一切都取决于。如果你只想向前移动,并且只删除当前节点,那么最好的选择是在执行比较交换位,跳跃(获取它的下一个节点)和删除时锁定的项目中有下一个锁定节点。

答案 1 :(得分:1)

我认为卡斯塔有一些好处,我会自己捡到一个。这个问题的解决方案将严重影响在每个节点上完成的思想转换,无论它们是否相互依赖,以及完成的任务是否可以在列表的单个扫描中完成。

如果操作不是相互依赖的,并且扫描方法有意义,我建议使用代理提取系统。它实际上与工作人员方法相同。每个线程都交给一个管理列表的代理的引用,因为每个线程需要内容来处理它,它从代理请求它,它锁定,获取下一个节点,推进内部扫描迭代器,并释放锁,返回节点到请求线程。这将继续,直到列表枚举完成。对于累积器问题,其中每个节点可能被不同的线程多次访问,您可以使用循环列表或其他一些此类容器。一个有天赋的经纪人可以管理列表,包括新的插入,删除等,与调度相同:锁定,操作,解锁。显然,很多活动可以根据您在经纪方的具体需求进行定制。这些需求可以建立一个精心设计的池管理系统,同时在锁定争用方面仍然非常有效(即几乎没有)。

然而,底线显然是显而易见的。了解您的问题集以及您希望每个线程使用其当前节点完成的具体细节。

答案 2 :(得分:0)

基本上,要尽快浏览列表,您需要避免一些事情;

  • 锁定碰撞。即使使用自旋锁,每次循环迭代也都错过了完成工作的机会。
  • 记忆障碍。每次进行原子操作时,内存屏障都会暂停执行。
  • 不必要的工作,例如扫描列表以便进行工作。

我必须同意你的阅读并采取自旋锁的一面。

您将指针放在易失性指针中的列表头部。

然后

每个线程依次;

  • 采取自旋锁
  • 将指针的值保存在临时
  • 更新指向下一个列表条目的指针
  • 释放自旋锁。

然后它可以开始处理临时指向的条目。

使用每个条目锁定来查找列表有一些优点;

  • 如果每个项目的工作需要非常重要的时间来完成,那么在更新指针的短暂持续时间内,您将只有很少的冲突。
  • 除非发生碰撞,否则每个列表项只会有2个内存屏障,一个用于锁定,一个用于解锁。
  • 不扫描列表以获取新工作项,只需从“队列”中获取工作。

答案 3 :(得分:0)

由于对齐,列表指针的低位包含零。您可以通过使用比较和交换指令将对象标记为正在处理来原子地设置其中一个指针中的一个位来利用它。

每个帖子都可以:

  1. 从头开始遍历列表。
  2. 对于每个列表节点,检查其下一个指针是否设置了较低位。
  3. 如果该位设置为goto 7。
  4. 比较并将列表节点的下一个指针与(next | 1)交换。
  5. 如果比较和交换失败转到7。
  6. 处理对象。
  7. 移至下一个节点并转到2。
  8. 另一种选择是将此位标记放入一个在对象本身中具有未使用位的整数,除非列表节点是基类或对象的成员。

    这样线程就可以从列表中获取对象而不会阻塞或忙于在wait-free fashion中旋转。