我读了一篇关于多线程程序设计http://drdobbs.com/architecture-and-design/215900465的文章,它说这是“用异步消息替换共享数据的最佳实践。尽可能地,更喜欢保持每个线程的数据被隔离(非共享),并让线程通过传递数据副本的异步消息进行通信“。
让我感到困惑的是,我没有看到使用共享数据和消息队列之间的区别。我现在正在开发一个关于Windows的非gui项目,所以让我们使用windows的消息队列。并以传统的生产者 - 消费者问题为例。
使用共享数据时,会有一个共享容器和一个锁定生产者线程和使用者线程之间容器的锁。当生产者输出产品时,它首先等待锁定然后向容器写入内容然后释放锁定。
使用消息队列,生产者只需PostThreadMessage即可。这是异步消息的优势。但我认为必须存在一些锁定两个线程之间的消息队列,否则数据肯定会被破坏。 PostThreadMessage调用只是隐藏细节。我不知道我的猜测是否正确但是如果它是真的,优势似乎不再存在,因为两种方法都做同样的事情,唯一的区别是系统在使用消息队列时隐藏细节。
PS。也许消息队列使用非阻塞包含器,但我也可以使用前一种方式的并发容器。我想知道消息队列是如何实现的,两种方式之间是否有任何性能差异?
更新: 如果消息队列操作仍然在其他地方被阻止,我仍然不会得到异步消息的概念。如果我猜错了,请纠正我:当我们使用共享容器和锁时,我们将在我们自己的线程中阻塞。但是当使用消息队列时,我自己的线程立即返回,并将阻塞工作留给某个系统线程。
答案 0 :(得分:7)
想象一下,您有1个线程生成数据,4个线程处理该数据(可能是为了使用多核机器)。如果您有一个庞大的全局数据池,则当任何线程需要访问时,您可能需要锁定它,可能会阻塞其他3个线程。当您添加更多处理线程时,您将增加锁定等待和增加可能需要等待的内容的几率。最终添加更多线程无法实现,因为您所做的只是花费更多时间来阻止。
如果您有一个线程将消息发送到消息队列,每个消费者线程一个,则它们不能相互阻塞。您必须在生产者和消费者线程之间锁定队列,但由于每个线程都有一个单独的队列,因此您有一个单独的锁,并且每个线程都无法阻止所有其他线程等待数据。
如果你突然得到一个32核的机器,你可以增加20个处理线程(和队列),并期望性能会相当线性地扩展,这与新线程将一直相互碰撞的第一种情况不同。 p>
答案 1 :(得分:5)
消息传递 对于交换较少量的数据非常有用,因为不需要避免冲突。实现比用于计算机间通信的共享内存容易得多。此外,正如您已经注意到的那样,消息传递的优势在于应用程序开发人员无需担心共享内存等保护的细节。
共享内存 可以实现最大的通信速度和便利性,因为它可以在计算机内以内存速度完成。共享内存通常比消息传递更快,因为消息传递通常使用系统调用来实现,因此需要更耗时的内核干预任务。相反,在共享内存系统中,仅需要系统调用来建立共享内存区域。一旦建立,所有访问都被视为正常的内存访问,没有来自内核的额外帮助。
编辑:您可能希望实现自己的队列的一种情况是要生成和使用大量消息,例如日志记录系统。随着PostThreadMessage的实现,其队列容量是固定的。如果超出容量,消息将最容易丢失。
答案 2 :(得分:2)
当您传递消息时,当然有“共享数据”。毕竟,消息本身就是某种数据。但是,重要的区别是当您传递消息时,消费者将收到副本。
PostThreadMessage调用只是隐藏细节
是的,确实如此,但作为一个WINAPI电话,您可以合理地确定它是正确的。
如果消息队列操作仍在其他地方被阻止,我仍然没有得到异步消息的概念。
优势更安全。您有一个在传递消息时系统强制执行的锁定机制。你甚至不需要考虑它,你不能忘记锁定。鉴于多线程错误是一些最糟糕的(考虑竞争条件),这是非常重要的。消息传递是基于锁的更高级别的抽象。
缺点是传递大量数据可能会很慢。在这种情况下,您需要使用need共享内存。
对于传递状态(即工作线程向GUI报告进度),消息是可行的方式。
答案 3 :(得分:2)
我使用了一个共享内存模型,其中指向共享内存的指针在一个消息队列中进行管理并小心锁定。从某种意义上说,这是消息队列和共享内存之间的混合体。当必须在线程之间传递大量数据同时保留消息队列的安全性时,这就非常了。
整个队列可以打包在单个C ++类中,并具有适当的锁定等。关键是队列拥有共享存储并负责锁定。生产者获取锁定以输入队列并接收指向下一个可用存储块(通常是某种对象)的指针,填充并释放它。消费者将阻塞,直到生产者发布下一个共享对象。然后,它可以获取对存储的锁定,处理数据并将其释放回池中。在适当设计的队列中,可以高效地执行多个生产者/多个消费者操作。认为Java线程安全(java.util.concurrent.BlockingQueue)语义,但是指向存储的指针。
答案 4 :(得分:1)
我认为这是信息的关键部分:“尽可能地,更愿意保持每个线程的数据被隔离(非共享),并让线程通过传递数据副本的异步消息进行通信”。即使用生产者 - 消费者:)
您可以自己传递消息或使用操作系统提供的内容。这是一个实现细节(需要在c语言中完成)。关键是要避免共享数据,就像拥有由多个线程修改的相同内存区域一样。这可能导致很难找到错误,即使代码是完美的,它也会因为所有锁定而牺牲性能。
答案 5 :(得分:1)
这很简单(我很惊讶其他人写了这么长的回复!):
使用消息队列系统代替“原始”共享数据意味着您必须在中央位置只进行一次同步(锁定/解锁资源)。
使用基于消息的系统,您可以更高级地思考“消息”,而不必再担心同步问题。对于它的价值,完全有可能在内部使用共享数据实现消息队列。
答案 6 :(得分:0)
我有同样的问题。看完答案后。我觉得:
在大多数典型用例中,queue = async,共享内存(锁)= sync。实际上,您可以执行共享内存的异步版本,但这样的代码更多,类似于重新发送消息传递轮。
更少的代码=更少的错误和更多的时间专注于其他东西。
以前的答案已经提到了利弊,所以我不再重复了。