在使用链表时我们如何使用多线程

时间:2013-12-10 14:13:55

标签: c++ multithreading linked-list multiprocessing

我对多线程的概念相当新,并且正在探索一些有趣的问题,以便获得更好的想法。

我的一位朋友建议如下:

“拥有一个链表并进行常规的插入,搜索和删除操作是相当简单的。但是如果多个线程需要在同一个列表上工作,你将如何进行这些操作呢? 最少需要多少锁。我们可以拥有多少个锁来优化链表功能?“

考虑一下,我觉得单锁就足够了。我们为每一次读写操作获取锁。我的意思是当我们访问列表中的节点数据时,我们获得了锁。当我们插入/删除元素时,我们获得完整系列步骤的锁定。

但我无法想到使用更多锁的方式会给我们带来更优化的性能。

任何帮助/指针?

5 个答案:

答案 0 :(得分:3)

“每个列表一个锁定”的逻辑扩展名为“每个项目一个锁定”。

这种情况有用的情况可能是如果你经常只修改列表中的单个项目。

对于删除和插入,获取正确的锁会变得更加复杂。您必须获取该项目之前和之后的锁定,并且您必须确保始终以相同的顺序获取它们(以防止死锁)。如果必须修改根元素,也可以考虑特殊情况(如果它是双链表或循环链表,也可能需要考虑)。由于更复杂的锁定逻辑导致的这种开销可能导致您的实现再次变慢,特别是如果您经常需要从列表中插入和删除。 因此,如果大多数访问是对单个节点的修改,我只会考虑这个。

如果您正在搜索特定用例的最佳性能,那么最后,它归结为实现两者,并针对典型方案运行性能比较。

答案 1 :(得分:1)

您肯定需要至少一个信号量/锁来确保列表完整性。

但是,假设列表上的任何操作最多改变两个节点:插入/更改/删除的节点以及指向它的相邻节点。因此,您可以基于每个节点实现锁定,为给定操作锁定最多两个节点。当不同的线程访问列表时,这将允许一定程度的并发性,尽管您需要区分读取和写入锁定以获得此方法的全部好处。

答案 2 :(得分:1)

如果您是多线程新手,请接受过早优化浪费时间的观念。链接列表是一种非常直接的数据结构,您可以通过在所有读取和写入上放置一个关键部分来使其成为线程安全的。这些将在执行读/插入/删除操作期间将线程锁定到CPU中,并确保线程安全。它们也不会消耗互斥锁的开销,也不会消耗更复杂的锁定机制。

如果您想在事后进行优化,只能使用有效的分析工具来为您提供原始数字。链接列表操作永远不会成为应用程序减速的最大来源,并且可能永远不值得你加入正在讨论的节点级锁定。

答案 3 :(得分:1)

对整个列表使用一个锁将首先完全打败多线程的大部分原因。通过锁定整个列表,您可以保证一次只能有一个线程使用该列表。

这肯定是安全的,因为你没有死锁或种族,但它是天真和低效的,因为你序列化对整个列表的访问。

更好的方法是为列表中的每个锁定一个锁,为列表本身锁定另一个。在附加到列表时将需要后者,具体取决于列表的实现方式(例如,如果它保持节点数与节点本身分开)。

然而,根据许多因素,这也可能不是最佳的。例如,在某些平台上,互斥量在实例化互斥锁时在资源和时间方面可能很昂贵。如果空间非常宝贵,另一种方法可能是拥有固定大小的互斥体池,只要您需要访问项目,就可以从中绘制。这些互斥锁会有某种所有权标志,指示它们被分配到哪个节点,因此不会同时为该节点分配其他互斥锁。

另一种技术是使用读/写锁,这将允许对任何线程的读访问,但只能对一个进行写访问,这两个是互斥的。然而,在文献中已经提出,在许多情况下使用读取器/写入锁实际上比仅使用普通互斥锁效率低。这取决于您的实际使用模式以及锁的实现方式。

答案 4 :(得分:0)

你只需要在写作时锁定,并且你说通常只有一次写入,所以尝试读/写锁。