如何在处理链表时使用多线程

How can we use multi-threading while working with linked lists

本文关键字:多线程 链表 处理      更新时间:2023-10-16

我对多线程的概念还很陌生,为了更好地了解问题,我一直在探索一些有趣的问题。

我的一个朋友建议如下:

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

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

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

有什么帮助/建议吗?

"每个列表一个锁"的逻辑扩展将是"每个项目一个锁。

如果你经常只修改列表中的一个项目,那么这将是有用的。

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

如果您正在为特定的用例搜索峰值性能,那么最终,它可以归结为实现两者,并为典型场景运行性能比较。

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

但是,据推测,列表上的任何操作最多更改两个节点:插入/更改/删除的节点和指向它的相邻节点。因此,您可以在每个节点的基础上实现锁定,为给定的操作最多锁定两个节点。当不同的线程访问列表时,这将允许一定程度的并发性,尽管我认为需要区分读锁和写锁才能充分利用这种方法。

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

如果你想在事后进行优化,只需使用一个有效的分析工具,它会给你原始数字。链表操作永远不会成为应用程序速度减慢的最大来源,添加正在讨论的节点级锁定可能永远都不值得。

对整个列表使用一个锁将完全击败多线程的大多数原因。通过锁定整个列表,可以保证一次只有一个线程可以使用该列表。

这当然是安全的,因为您不会出现死锁或争用,但这是幼稚和低效的,因为你序列化了对整个列表的访问。

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

然而,这也可能不是最佳的,这取决于许多因素。例如,在某些平台上,在实例化互斥体时,互斥体在资源和时间方面可能会很昂贵。如果空间非常宝贵,另一种方法可能是拥有一个固定大小的互斥池,每当需要访问项目时,都可以从中提取互斥。这些互斥体将具有某种所有权标志,该标志指示它们被分配给哪个节点,这样就不会同时将其他互斥体分配给该节点。

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

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