我正在开发一个小型多线程项目。系统可分为A和B两个子部分。数据从A流向B. 一部分不断从外部获取原始数据,进行一些转换,然后生成数千个新数据,我们称之为A_OUTPUT。 B部分根据每个A_OUTPUT进行一些计算,然后生成更多数据,可能是A_OUTPUT数量的十倍。
我对如何同步2个部分感到困惑 我自己的设计是创建一个工作队列以及一个保护两个子部分之间队列的锁。还要创建一个事件来指示工作队列是否为空。
一个部分包含多个线程,每个线程从外部获取数据并生成A_OUTPUT,每次单个A线程生成A_OUTPUT,线程获取队列锁定,将A_OUTPUT推入队列,释放锁定,然后触发事件。
B部分包含一个主管线程和几个工作线程,主管线程首先在事件中被阻止。触发事件后,管理程序线程锁定队列,获取队列的所有A_OUTPUTS,释放锁定,将A_OUTPUTS分派给工作线程,然后再次等待事件。
这种设计的问题很明显,B的主管线程将与A的多个线程竞争以赢得队列锁定。也许当B最终拥有锁时,队列中已经有十个或更多的A_OUTPUT,并且很久以前就生成了最老的A_OUTPUT。我希望尽可能快地处理每个A_OUTPUT。
我知道我可以将工作队列划分为几个较小的队列,或者在锁定战中添加更多的B管理员线程,以缩短每个A_OUTPUT在处理之前等待的平均时间。但是可能存在更合适的设计吗?
另一个问题是,对于不同目的的多线程程序是否存在任何范例或设计模式?
答案 0 :(得分:3)
我可以推荐以下方法:
mutex同步对队列的访问权限。保留两个condition variables,一个表示队列未满(您需要处理Producer
产生的数据多于Consumer
可以消耗的情况),另一个表示该队列有任何数据
Producer
检查队列是否已满。如果满 - 条件“未满”,否则产生一些数据,将其放入队列,通知“有数据”状态。
Consumer
检查队列是否有任何数据,消耗它并通知“未满”状态
此外,您可以使用无锁队列以获得更好的性能。检查TBB或最近宣布的 Boost.Lockless (目前正在审核中)。顺便说一下,使用TBB整个任务要简单得多,只需使用他们的调度程序和容器就可以了 显式同步
答案 1 :(得分:1)
与@thiton一样,我认为不需要主管线程 - 这似乎是一种不必要的复杂问题?
这似乎是流控制和队列设计的问题。鉴于您似乎需要,我会选择两个生产者 - 消费者队列和有限数量的A_OUTPUT实例。我会创建1000个(比方说)A_OUTPUT实例并将它们推送到一个P-C队列,在启动时形成一个线程安全的对象池(池队列)。 A线程启动,从池中弹出A_OUTPUT并开始“从外部世界获取原始数据”。当A线程在其A_OUTPUT中获取数据时,它会将其推送到另一个P-C队列(通信队列)。一个B线程池正在等待通信队列。当一个A_OUTPUT在通信队列上可用时,一个B线程将获得它并处理它。当B线程完成数据后,将A_OUTPUT推回池队列。因此,A_OUTPUT实例循环,将数据从系统的一端传送到另一端,然后通过池队列返回到开始。
这样的设计允许跨多个线程/池进行流控制。队列中有足够的“松弛”以允许突发高负载,但是无法使用失控的线程/对象 - 如果有太多数据流过,A线程会发现池空并阻塞它直到A_OUTPUT实例变为可用 - 当是,A线程将继续获取更多数据。
可以在运行时调整这样的系统。添加/删除A / B线程池中的线程并增加/减少对象池深度很容易。
哦 - 你不需要复杂的有界P-C队列。如果每个队列都可以容纳池中的对象数量,那就足够了 - 不会再有了。
RGDS, 马丁
答案 2 :(得分:0)
您可以通过限制队列大小来控制队列年龄问题,就像Andy T建议的那样。
此外,如果删除主管线程,您的布局会变得更加简单。 A的线程在完成处理时获取互斥锁,将它们的项放入队列,并等待队列足够空以继续。每个消费者线程都会等待,直到队列中的某个项可用,获取它并使用它。
由于A和B的线程数足够接近,因此不太可能出现饥饿现象。此外,您应该使用诸如pthread_cond_signal(而不是pthread_cond_broadcast)之类的方法,这些方法只从每一侧唤醒一个线程,从而最大限度地减少了比赛次数。
答案 3 :(得分:0)
这里有一篇文章可能有所帮助: http://drdobbs.com/showArticle.jhtml?articleID=184401751
(哎呀,刚刚注意到这是一个旧线程,它只是一小时前编辑的答案。)