C++中的多线程链表

multithreaded linked list in C++

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

首先对我要做的事情进行一点解释:

我的计划是编写一个带有使用boost::asio库实现的套接字流的程序,该库将数据提供给使用boost:spirit::qi实现的解析器。解析器将获取数据包并填充一个数据包对象,然后将该对象附加到数据包对象链表的末尾。数据包处理器将读取列表中的第一个对象并进行处理,然后移动到下一个项目并删除第一个。

我决定使用链表,因为如果我使用std::队列,每次流添加数据包或处理器删除数据包时,我都必须锁定整个容器,这会使两个线程或多或少串行运行,我希望避免这种情况。另外,队列类有复制整个对象的趋势,而链表的想法有一个好处,那就是只创建一次对象,然后指向它。为了避免序列化整个业务,我打算在每个节点中放置boost:互斥互斥,并从那里锁定它们。其想法是让套接字流创建列表并立即锁定第一个节点,从解析器填充节点,创建下一个节点并锁定它,然后解锁第一个节点并转到下一个结点进行工作。这样,就永远不会有一个未锁定的节点挂在最后,数据包处理程序可以跳到套接字流鼻下并删除它。数据包处理器将检查第一个节点并尝试锁定它,如果它锁定了它,则它将进行处理,然后解锁它,获取下一个节点,然后删除第一个节点。这种序列化方式仅限于数据包处理器赶上套接字流类的时候。

所以现在我的问题是,在我真正实现这项工作之前,这听起来是个好主意吗?我在一个琐碎的测试中尝试过它,它似乎工作得很好。我想不出有什么严重的问题,只要我实现异常处理并注意释放我分配的任何内存,但如果有人能想到我忽略的这个想法的任何问题,我将不胜感激。此外,我也很感激任何人可能提出的其他建议,无论是作为替代方案,还是可能使这个想法更好地发挥作用。

提前感谢!

这个实现让我尖叫三件事:

  • 死锁太容易了,因为插入和删除需要同时锁定多个互斥对象,而不能同时锁定。嗯,你可以,但你需要在你的互斥对象周围放置互斥对象
  • 腐败太容易了。导致僵局的问题也可能导致腐败
  • 慢,慢,慢。想想你可怜的列表浏览者。每一步都涉及一次解锁、一次锁定、另一次解锁和另一次锁定。必须非常小心,伙计,这会很贵吗。锁定和解锁每个项目,并按照正确的顺序进行?哎哟

这看起来像是一个过早优化和过早悲观化并行操作的情况。是什么推动了这种架构?

我建议先从简单的解决方案开始。每当你想触摸它的任何部分时,都要把它锁起来。看看这是否会给你带来麻烦。若并没有,问题就解决了。如果是这样,下一步就是从互斥锁切换到读写锁。只是一个,不是一大堆。现在你必须考虑一下你想要共享锁还是独占锁。如果您一直在关注const的正确性,请尝试为您的const方法使用共享锁(读锁),为非const方法提供独占锁(写锁)。

我认为你的建议行不通。请记住,从链表中删除节点时,需要更新指向已删除节点的其他节点。类似地,添加节点时,还需要更新其他节点以指向新节点。因此,仅仅锁定正在删除或添加的节点是不够的。

有一些无锁队列,但它们极难纠正。例如,查看本文的初始注释,其中描述了使已发布的算法工作所需的额外工作。

即使您将其称为链表,实际上这也是一个队列。

如果您愿意使用固定大小的缓冲区,则可以实现Single Producer Single Consumer免锁队列。这样,如果使用者的速度不够快,您就可以控制内存的使用,而代价是让生产者等待。

除了这点,你的设计看起来不错;它可能会比无锁的替代方案更容易做到正确。

请记住要有一个终止条件(例如,next字段中的空指针),以便生产者可以向消费者发出信号,表示没有更多的事情要处理。

嗯。。为什么一个常见问题的解决方案如此复杂?有很多生产者-消费者队列类——选择一个有效的引用/pointer/int大小的类(即不复制数据)。

从流中获取字节,直到您根据协议组装了一个"数据包对象"。将数据包对象引用推到队列中,并立即为下一个数据包新建()另一个。数据包处理器使用队列中的数据包对象。

队列仅在向队列推送/从队列弹出对象引用所花费的时间内被锁定。套接字/流回调所组装的包对象和包处理器所处理的包对象总是不同的,因此不需要锁定。

试图对已经在队列中的对象(或类似队列的链表)进行操作,对我来说听起来像是一场噩梦,(其他海报似乎也同意这一点)。你需要这样做还有其他原因吗?

Rgds,Martin