我已经制作了一系列4个 生产环境 者 - 消费者线程(形成一个4步骤的管道) . 令我惊讶的是,所有四个线程都按顺序运行,而不是同时运行!也就是说,第二个线程神秘地等待,直到第一个线程完全完成生成 . 第三个线程神秘地等待,直到第二个线程完全完成生成(等等) .
它变得更糟 . 如果我将Thread.Sleep(300)放入第一个生成器的循环中,那么其他三个线程将变为并发并实际上获得处理器时间,如预期的那样,从多线程应用程序产生"random interleaved"控制台输出 . 我几乎无法接受这样的想法,即"sleep"是解决方案的必要部分,但我看到睡眠在code written by Jon Skeet中以完全相同的方式被合并 .
请告诉我没有必要实现并发,或者如果是,那么 why ?
关于我的特定 生产环境 者 - 消费者链的更精确的故事如下:
809 First thread: 在紧密循环中它尽可能快地生成"widget"消息,将它们推送到下一个线程的队列中 . 将第一个窗口小部件添加到队列时,System.Threading.Timer设置为~100毫秒 . 从计时器发出的回调是第二个线程......
-
Second thread (fired from timer): 从先前队列中读取部分或全部小部件 . 它将它们发送到另一个队列(由第三个线程消耗) . monitor.Pulse / Wait机制用于与第三个线程同步 .
-
Third thread: 监视器上的块 . 等到调用monitor.Pulse,然后从队列中取出一个项目 . 推送完成后,再次使用monitor.Pulse将一个项目推入最终队列 .
-
fourth thread: 监视器上的块 . 等到调用monitor.Pulse . 窗口小部件已处理 .
通过此管道处理100万个小部件大约需要4分钟 . 在4分钟内,最后3个线程的安排时间很长,并且与第一个线程同时工作 . 但正如我所说,最后三个线程按顺序运行,除非我为第一个线程引入一个小睡眠 . 这没有道理 .
关于为什么这样工作的任何想法?
附:请不要告诉我,正如我所描述的那样,长期的 生产环境 者 - 消费者链可以缩小或消除 . 请相信我(或假设)我需要一个很长的链条 . :)
2 回答
紧密循环会影响多线程 . 听起来你的第一个线程运行得足够快,第二个线程甚至没有机会启动 . Note 如果发生这种情况,那么顺序解决方案是最有效的 . :)
由于您的 生产环境 者/消费者布局有些复杂,我假设您没有看到真实数据的这种行为 .
虽然您可以通过添加带有非零参数的
Thread.Sleep
来解决此问题(请参阅Joe Duffy's blog entry on why this works)或者忽略它,但更好的解决方案是限制第一个 生产环境 者/消费者队列的大小 . 这将只允许第一个线程生成一定数量的小部件,然后阻塞,直到管道的其余部分有机会启动 ..NET 4.0
BlockingCollection<T>
允许您指定最大大小 . Microsoft Rx library已将此向后移植到.NET 3.5,因此您可以根据需要使用它 . (我建议不要使用内部解决方案) .让我猜一下 - 你最有可能使用某种锁定机制?我建议对消息队列进行无锁实现以提高性能 . 无论是那个还是你强迫他们都进入同一个硬件线程,但我不确定你是否可以在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
我还应该指出竞争条件并使用高速缓存大小的内存块来写入您写入的空间,并将其与您正在读取的高速缓存行分开,以避免一个线程强制另一个线程从主内存重新获取数据 .
希望有所帮助