3

首先解释一下我正在尝试做的事情:

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

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

所以现在我的问题是,在我开始实际实施之前,这听起来是个好主意吗?我已经在一个简单的测试中尝试过它,它似乎工作正常,只要我实现异常处理并注意释放我分配的任何内存,我想不出任何严重的问题,但如果有人能想到我忽略了这个想法的任何问题,我将不胜感激。另外,我将不胜感激任何其他人可能会提出的任何其他建议作为替代方案,或者可能会使这个想法更好地发挥作用。

提前致谢!

4

5 回答 5

2

查看这篇文章,它是关于多个消费者的,但仍然很精彩:

测量并行性能:优化并发队列

于 2011-07-02T14:50:18.410 回答
0

我不认为你的建议会奏效。请记住,当您从链表中删除一个节点时,您需要更新指向已删除节点的其他节点。同样,当您添加一个节点时,您还需要更新其他节点以指向新节点。所以仅仅锁定被删除或添加的节点是不够的。

有无锁队列,但它们非常难以正确。例如,请查看此处对文章的初始评论,该评论描述了使已发布算法正常工作所需的额外工作。

于 2011-07-02T14:51:50.763 回答
0

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

如果您愿意使用固定大小的缓冲区,则可以实现单生产者单消费者无锁队列。如果消费者不够快,这让您可以以让生产者等待为代价来控制内存使用。

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

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

于 2011-07-02T15:02:17.170 回答
0

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

  • 太容易死锁了,因为插入和删除需要同时锁定多个互斥锁,而你不能同时这样做。好吧,你可以,但你需要在你的互斥体周围放置互斥体。
  • 太容易贪污了。导致死锁的问题也可能导致腐败。
  • 而且慢,慢,慢。想想你可怜的名单步行者。每个步骤都涉及一个解锁、一个锁定、另一个解锁和另一个锁定。走路时必须非常小心,伙计,它会很贵。锁定和解锁每个项目,并以正确的顺序进行?哎哟。

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

我建议先从简单的解决方案开始。每当您想触摸它的任何部分时,每次都锁定整个东西。看看会不会给你带来麻烦。如果没有,问题就解决了。如果是这样,下一步就是从互斥锁切换到读写锁。只有一个,而不是无数。现在您必须考虑一下是否需要共享锁或排他锁。如果您对 const 的正确性很认真,请尝试为您的 const 方法使用共享锁(读锁),为您的非 const 方法使用排他锁(写锁)。

于 2011-07-02T15:11:19.840 回答
0

嗯..为什么对一个常见问题有如此复杂的解决方案?那里有很多生产者 - 消费者队列类 - 选择一个可以工作的引用/指针/int大小的类(即没有数据复制)。

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

队列仅在将对象引用推入/弹出队列所需的时间内锁定。由套接字/流回调组装的数据包对象和由数据包处理器处理的数据包对象总是不同的,因此不需要锁定。

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

Rgds,马丁

于 2011-07-02T16:16:23.913 回答