多线程访问的高效结构

时间:2010-09-03 09:46:16

标签: c++ multithreading data-structures performance

我需要实现一个具有datastruture(此时为Queue)的机制,该机制包含待处理请求对象的列表,这些对象在使用时由不同的线程标记,并在线程完成使用时被取消。

这个数据结构在任何给定的时间内最多可以有几千个项目,N个线程将从它接收请求(基本上将其标记为'take')然后当线程完成时它将在其中找到相同的请求结构并将其删除。

现在我想知道C ++ STL队列在执行此操作方面的效果如何,以及在需要将其从队列中删除时必须再次查找相同的项目?

我不希望此数据结构被线程同步机制锁定太长时间,因为线程正在其中的某个位置查找项目。这可能会锁定我的整个程序。 (该程序需要非常高的性能和快速)

有人可以建议如何在多线程环境中最好地实现这一点,以便在需要执行搜索时,结构不会被长时间锁定吗?

3 个答案:

答案 0 :(得分:1)

您可能会关注设计中最困难的部分。

如果队列是FIFO而没有任何优先级,那么你的访问器将是push_back()和pop_front() - 即使你没有遇到使用比较和交换(CAS)语义的麻烦,但是非常快坚持使用简单的互斥/关键部分。如果您需要优先处理流量,那么事情会变得更加困难。如果你确实使用CAS锁定(那么在Windows上)你无法改进boost :: thread的shared_mutex而不花费太多时间来编写这部分代码。不确定非Windows实现。

此问题中更复杂的部分通常是发出空闲工作线程信号来接收新工作。在queue.front()非空之前,您不能让它们循环,因此您需要一种方法来确保获取正确数量的空闲线程以获取排队的项目。当工作线程空闲时,它可以检查新工作并执行,如果不是,则需要将队列状态设置为空闲,以便下一个push_back导致“唤醒”踢,以重新启动工作线程池。对于所有非致命异常,此区域必须100%稳健,否则您的过程将变暗。

您是在管理自己的线程还是使用内置线程池?您是否计划拥有一个动态大小的线程池,或者只是产生N个线程(可能是可配置的)并让它们运行直到进程退出?

绝对让工作线程执行工作项过程的记录。了解谁在其生命周期的任何部分拥有工作项是至关重要的。停止/开始工作,工作项目摘要和时间将是有用的。如果日志记录很慢,那么通过即发即弃队列将其推送到一个单独的线程,但是你必须注意延迟,这会使你的日志变得不那么有用。如果您确实需要外部操作正在进行的工作项的能力,那么与待处理工作队列中的单独结构 - 正在进行的工作项目由线程索引并显示当前状态/开始时间,具有单独锁定,听起来像个好主意。这个结构将是O(线程计数),因此小于“挂起”队列,因此扫描它不太可能成为瓶颈,只要在结构锁定之外完成长时间运行的结果操作。

关于性能 - 你的工作线程将会做什么?如果工作项将长期运行,执行大量I / O或其他昂贵的操作,那么队列交互不是您的性能瓶颈,因此过度优化该区域相对没有效果。考虑设计中整个系统的性能,而不仅仅是一个小区域。

这仅适用于初学者。祝你好运,这不是一个容易设计的简单系统。

[编辑]基于工作项目描述。

解析应该很快(尽管可能涉及昂贵的源数据检索 - 很难说?),DB访问不那么频繁。听起来像调整数据库可能是你最大的每次降压。如果您无法控制这一点,那么您只需尽可能减轻设计中的慢速数据库。如果您可以选择执行异步数据库访问,那么工作线程可以做足够的工作来启动数据库调用,然后完成回调工作,允许其他工作在工作线程上启动。如果没有异步数据库访问,如果没有一些替代的间接方法(主工作线程不等待数据库调用完成内联),则很难实现可靠的请求超时。您需要将主要工作线程与对数据库的依赖性分离,除非您可以信任数据库以及时返回或错误输出。数据库请求可能有一些可配置或特定于工作项的超时?通常,DB API库允许这样做。

您的超时监视器需要了解工作项状态。可能在您的工作项目上有一些虚拟的Cancel()方法,以确保清理超时项目的灵活性。

答案 1 :(得分:1)

Quoting Herb Sutter:

  

链接列表非常棒   兼容并发的数据结构   因为他们支持高度本地化   更新。特别是,如图所示   在图1中,插入一个新节点   一个双重链表,你只需要   触摸两个现有节点;即,   紧邻的那些   新节点将占用的位置   将新节点拼接到列表中。至   擦除节点,你只需要触摸   三个节点:正在存在的节点   擦除了,它的两个立即   相邻节点。

除此之外,我同意你应该在处理它之前从队列中删除该项目的评论。但我可能错了,因为我不知道你申请的细节。

答案 2 :(得分:0)

仔细看看Herb Sutters“Effective Concurrency”系列(很快就会成为一本书)。

在食用之前总是从队列中删除项目 - 你还在树上吃苹果吗?

简而言之:从queue / singly-linked-list中删除东西时,请使用比较和交换原子操作,或者在Windows中说明InterlockedExchangePointer。这将始终允许一个线程向前移动。 Boost中可能存在类似的功能。

同时将日志记录移动到正在进行消费的类中。