.net中的生产者消费者链和线程调度

时间:2010-08-20 22:22:00

标签: .net multithreading synchronization

我已经制作了一系列4个生产者 - 消费者线程(形成一个4步骤的管道)。令我惊讶的是,所有四个线程都按顺序运行,而不是同时运行!也就是说,第二个线程神秘地等待,直到第一个线程完全完成生成。第三个线程神秘地等待,直到第二个线程完全完成生成(等等)。

情况变得更糟。如果我将Thread.Sleep(300)放入第一个生成器的循环中,那么其他三个线程将变为并发并实际获得处理器时间,如预期的那样,产生“随机交错”控制台输出,如预期的多线程应用程序。我几乎无法接受“睡眠”是解决方案的必要部分的想法,但我发现在code written by Jon Skeet中以完全相同的方式结合了睡眠。

请告诉我没有必要实现并发,或者如果是,那么为什么

关于我的特定生产者 - 消费者链的更精确的故事如下:

  1. 第一个线程:在紧密循环中它尽可能快地生成“窗口小部件”消息,将它们推送到下一个线程的队列中。将第一个窗口小部件添加到队列时,System.Threading.Timer设置为~100毫秒。从计时器触发的回调是第二个线程...
  2. 第二个线程(从计时器触发):从先前队列中读取部分或全部小部件。它将它们发送到另一个队列(由第三个线程消耗)。 monitor.Pulse / Wait机制用于与第三个线程同步。
  3. 第三个线程:监视器上的块。等到调用monitor.Pulse,然后从队列中取出一个项目。一个项目被推入最终队列,再次使用monitor.Pulse进行推送。
  4. 第四个线程:监视器上的块。等到调用monitor.Pulse。窗口小部件已处理。
  5. 通过此管道处理100万个小部件大约需要4分钟。在4分钟内,最后3个线程的安排时间很长,并且与第一个线程同时工作。但正如我所说,最后三个线程按顺序运行,除非我为第一个线程引入一个小睡眠。这毫无意义。

    有关为什么会这样运作的任何想法?

    P.S。请不要告诉我,正如我所描述的那样,长期的生产者 - 消费者链可以缩小或消除。请相信我(或假设)我需要一个很长的链条。 :)

2 个答案:

答案 0 :(得分:1)

紧密循环会影响多线程。听起来你的第一个线程运行得足够快,第二个线程甚至没有机会启动。 注意如果发生这种情况,则顺序解决方案效率最高。 :)

由于您的生产者/消费者布局有些复杂,我假设您没有看到真实数据的这种行为。

虽然您可以通过添加Thread.Sleep非零参数(请参阅Joe Duffy's blog entry on why this works)或忽略它来解决此问题,但更好的解决方案是限制第一个生产者/消费者的大小队列。这将只允许第一个线程生成一定数量的小部件,然后阻塞,直到管道的其余部分有机会启动。

.NET 4.0 BlockingCollection<T>允许您指定最大大小。 Microsoft Rx library已将此移植到.NET 3.5,因此您可以根据需要使用它。 (我建议不要使用内部解决方案)。

答案 1 :(得分:0)

让我猜一下 - 你最有可能使用某种锁定机制?我建议对消息队列进行无锁实现以提高性能。无论是那个还是你都强迫他们都进入同一个硬件线程,但我不确定你是否可以在Windows上做到这一点。

我建议开始阅读这个与1个生产者和1个消费者一起使用的消费者 - 生产者队列的无锁实现: 没有锁的多线程单生产者单一消费者: http://www.codeproject.com/KB/threads/LockFree.aspx#heading0005

我还建议你阅读一些关于volatile的内容: http://www.drdobbs.com/cpp/212701484

Herb Sutter撰写了一篇很好的文章,提醒您编写此类代码的危险性:http://www.drdobbs.com/cpp/210600279;jsessionid=ZSUN3G3VXJM0BQE1GHRSKHWATMY32JVN?pgno=2

最后,我建议您阅读本文以获取另一个无锁队列:http://www.drdobbs.com/architecture-and-design/210604448

我还应该指出竞争条件并使用缓存大小的内存块来写入您写入的空间,并将其与您正在读取的缓存行分开,以避免一个线程强制另一个线程重新获取数据来自主存。

希望有所帮助