如何在处理链表时使用多线程
How can we use multi-threading while working with linked lists
我对多线程的概念还很陌生,为了更好地了解问题,我一直在探索一些有趣的问题。
我的一个朋友建议如下:
"有一个链表并执行常规的插入、搜索和删除操作是相当简单的。但是,如果多个线程需要在同一个列表上工作,你该如何执行这些操作呢?"。最少需要多少把锁。我们可以有多少个锁才能拥有优化的链表函数?"
经过思考,我觉得一把锁就足够使用了。我们获取每个读写操作的锁。我的意思是,当我们访问列表中的节点数据时,我们会获得锁。当我们插入/删除元素时,我们会获取完整系列步骤的锁定。
但我想不出使用更多锁会给我们带来更优化的性能的方法。
有什么帮助/建议吗?
"每个列表一个锁"的逻辑扩展将是"每个项目一个锁。
如果你经常只修改列表中的一个项目,那么这将是有用的。
不过,对于删除和插入,获取正确的锁会变得更加复杂。您必须在之前和之后获取项的锁,并且必须确保始终以相同的顺序获取它们(以防止死锁)。当然,如果根元素必须修改(也可能是双链表或循环链表),也需要考虑特殊情况。这种由更复杂的锁定逻辑产生的开销可能会导致您的实现再次变慢,尤其是当您经常不得不从列表中插入和删除时。因此,只有当大多数访问是对单个节点的修改时,我才会考虑这一点。
如果您正在为特定的用例搜索峰值性能,那么最终,它可以归结为实现两者,并为典型场景运行性能比较。
您肯定需要至少一个信号量/锁来确保列表的完整性。
但是,据推测,列表上的任何操作最多更改两个节点:插入/更改/删除的节点和指向它的相邻节点。因此,您可以在每个节点的基础上实现锁定,为给定的操作最多锁定两个节点。当不同的线程访问列表时,这将允许一定程度的并发性,尽管我认为需要区分读锁和写锁才能充分利用这种方法。
如果您是多线程的新手,请接受过早优化是浪费时间的概念。链表是一种非常直接的数据结构,您可以通过在所有读写操作上放一个关键部分来确保线程安全。这将在执行读/插入/删除操作期间将线程锁定到CPU中,并确保线程安全。它们也不会消耗互斥锁或更复杂的锁定机制的开销。
如果你想在事后进行优化,只需使用一个有效的分析工具,它会给你原始数字。链表操作永远不会成为应用程序速度减慢的最大来源,添加正在讨论的节点级锁定可能永远都不值得。
对整个列表使用一个锁将完全击败多线程的大多数原因。通过锁定整个列表,可以保证一次只有一个线程可以使用该列表。
这当然是安全的,因为您不会出现死锁或争用,但这是幼稚和低效的,因为你序列化了对整个列表的访问。
更好的方法是为列表中的每个项设置一个锁,并为列表本身设置另一个锁。后者在附加到列表时是需要的,这取决于列表的实现方式(例如,如果它保持与节点本身分离的节点计数)。
然而,这也可能不是最佳的,这取决于许多因素。例如,在某些平台上,在实例化互斥体时,互斥体在资源和时间方面可能会很昂贵。如果空间非常宝贵,另一种方法可能是拥有一个固定大小的互斥池,每当需要访问项目时,都可以从中提取互斥。这些互斥体将具有某种所有权标志,该标志指示它们被分配给哪个节点,这样就不会同时将其他互斥体分配给该节点。
另一种技术是使用读写锁,这将允许对任何线程进行读访问,但只允许对一个线程进行写访问,两者互斥。然而,文献中已经提出,在许多情况下,使用读写锁实际上不如简单地使用普通互斥锁有效。这将取决于您的实际使用模式以及锁的实现方式。
您只需要在写入时锁定,并且您说通常只有一次写入,所以请尝试读/写锁定。
- 在C++中使用cURL和多线程
- 多线程双缓冲区
- 为什么我的多线程作业队列崩溃
- 在main()之外初始化std::vector会导致性能下降(多线程)
- 试图创建一个多线程程序来查找0-100000000之间的总素数
- 为什么一个向量上的多线程操作很慢
- 学习多线程C++:添加线程不会使执行速度更快,即使它看起来应该
- 全局变量 多读取器 一个写入器多线程安全?
- boost::文件系统::recursive_directory_iterator多线程安全
- 如何阻止TensorFlow的多线程
- 如何在多线程中正确使用unique_ptr进行多态性?
- 并发/多线程:是否可以以这种方式生成相同的输出?
- sigwait() 在多线程程序中不起作用
- 多线程程序中出现意外的内存泄漏
- 慢速插入链接列表需要多线程
- 多线程链表遍历
- 如何在处理链表时使用多线程
- 跨多个线程设置表项
- 链接时间优化与多线程支持冲突
- C++中的多线程链表