消息队列在线程通信中比共享数据有啥优势?

Posted

技术标签:

【中文标题】消息队列在线程通信中比共享数据有啥优势?【英文标题】:what's the advantage of message queue over shared data in thread communication?消息队列在线程通信中比共享数据有什么优势? 【发布时间】:2011-10-30 08:17:18 【问题描述】:

我读了一篇关于多线程程序设计http://drdobbs.com/architecture-and-design/215900465的文章,它说“用异步消息替换共享数据是一个最佳实践。尽可能地让每个线程的数据保持隔离(非共享),并让线程代替通过传递数据副本的异步消息进行通信”。

让我感到困惑的是,我看不出使用共享数据和消息队列之间的区别。我现在在 windows 上做一个非 gui 项目,所以让我们使用 windows 的消息队列。并以传统的生产者-消费者问题为例。

使用共享数据,在生产者线程和消费者线程之间会有一个共享容器和一个保护容器的锁。生产者输出产品时,先等待锁,然后向容器写入内容,然后释放锁。

使用消息队列,生产者可以简单地 PostThreadMessage 没有阻塞。这就是异步消息的优势。但我认为两个线程之间必须存在一些锁来保护消息队列,否则数据肯定会损坏。 PostThreadMessage 调用只是隐藏细节。我不知道我的猜测是否正确,但如果是真的,优势似乎不再存在,因为这两种方法做同样的事情,唯一的区别是系统在使用消息队列时隐藏了细节。

ps。也许消息队列使用非阻塞容器,但我也可以以前一种方式使用并发容器。我想知道消息队列是如何实现的,这两种方式有什么性能差异吗?

更新: 如果消息队列操作仍然被其他地方阻塞,我仍然不明白异步消息的概念。如果我的猜测是错误的,请纠正我:当我们使用共享容器和锁时,我们将阻塞在我们自己的线程中。但是当使用消息队列时,我自己的线程立即返回,并将阻塞工作留给某个系统线程。

【问题讨论】:

【参考方案1】:

消息传递对于交换少量数据很有用,因为不需要避免冲突。它比用于计算机间通信的共享内存更容易实现。此外,正如您已经注意到的,消息传递的优点是应用程序开发人员无需担心共享内存等保护的细节。

共享内存可实现最大的通信速度和便利性,因为它可以在计算机内以内存速度完成。共享内存通常比消息传递更快,因为消息传递通常使用系统调用实现,因此需要更耗时的内核干预任务。相反,在共享内存系统中,系统调用只需要建立共享内存区域。一旦建立,所有访问都将被视为正常的内存访问,无需内核的额外帮助。

编辑:您可能希望实现自己的队列的一种情况是要生成和使用大量消息,例如日志系统。随着 PostThreadMessage 的实现,它的队列容量是固定的。如果超出该容量,消息很可能会丢失。

【讨论】:

那么唯一的区别是消息传递隐藏了细节?如果我自己使用互斥体和队列,它至少应该和消息队列一样高效? 这取决于。系统提供的消息队列设计得很好,可以有效地处理同步。为什么需要重新发明***? 如果消息队列操作仍然被其他地方阻塞,我仍然没有得到异步消息的概念。如果我的猜测是错误的,请纠正我:当我们使用共享容器和锁时,我们将阻塞在我们自己的线程中。但是当使用消息队列时,我的线程立即返回,并将阻塞工作留给某个系统线程。 好收获。是的,使用共享内存,所有其他试图访问 CS 的线程都将被阻塞,直到当前线程存在。对于异步消息传递,只要队列缓冲区未满,发送线程就不会被阻塞。是否有一些内核线程被阻塞,这是我不确定的。如果队列上的基本操作(添加/删除)被实现为原语,那么它是微不足道的。【参考方案2】:

我认为这是其中的关键信息:“尽可能让每个线程的数据保持隔离(不共享),并让线程通过传递数据副本的异步消息进行通信”。 IE。使用生产者-消费者 :) 您可以进行自己的消息传递或使用操作系统提供的东西。这是一个实现细节(需要立即完成)。关键是要避免共享数据,因为同一内存区域被多个线程修改。这可能会导致难以发现错误,即使代码完美,也会因为所有锁定而消耗性能。

【讨论】:

使用消息传递仍然需要在两个线程之间共享数据。他们只是隐藏细节 当然可以,但它可以帮助您将共享降至最低。与让多个线程随机访问应用程序的所有数据不同。【参考方案3】:

假设您有 1 个线程产生数据,4 个线程处理该数据(大概是为了使用多核机器)。如果您有一个大的全局数据池,那么当 任何 个线程需要访问时,您可能不得不锁定它,这可能会阻塞 3 个其他线程。随着您添加更多处理线程,您增加了锁必须等待的机会并且增加了可能需要等待的事情的数量。最终添加更多线程将一事无成,因为您所做的只是花费更多时间阻塞。

如果您有一个线程将消息发送到消息队列中,每个消费者线程一个,那么它们就不能互相阻塞。您仍然必须锁定生产者和消费者线程之间的队列,但是由于每个线程都有一个单独的队列,因此您有一个单独的锁,并且每个线程不能阻塞所有其他等待数据的线程。

如果您突然获得一台 32 核的机器,您可以再添加 20 个处理线程(和队列),并期望性能会以相当线性的方式扩展,这与第一种情况不同,即新线程会一直相互运行。

【讨论】:

我不知道为什么没有人投票 - 你在共享内存阻塞方面提出了一个很好的观点!谢谢你,好头好哥们。老实说,关于你所说的方式 - 这应该是问题的答案:)【参考方案4】:

传递消息时当然有“共享数据”。毕竟,消息本身就是某种数据。但是,重要的区别是当您传递消息时,消费者将收到副本

PostThreadMessage 调用只是隐藏细节

是的,确实如此,但作为一个 WINAPI 调用,您可以有理由确定它是正确的。

如果消息队列操作仍然在其他地方被阻塞,我仍然不明白异步消息的概念。

优点是更安全。您有一个在传递消息时系统地强制执行的锁定机制。你甚至不需要考虑它,你不能忘记锁定。鉴于多线程错误是一些最讨厌的错误(想想竞争条件),这非常重要。消息传递是建立在锁之上的更高层次的抽象。

缺点是传递大量数据可能会很慢。在这种情况下,您需要使用需要共享内存。

对于传递状态(即工作线程向 GUI 报告进度),消息是要走的路。

【讨论】:

复制?当我传递消息时,我会非常努力地避免复制除对象指针之外的任何内容。为什么要复制批量数据?复制成本很高,而且通常需要锁定副本。 @MartinJames 如果您在消息中传递指针,那么您又回到了原点(即您实际上是在共享数据)并且必须进行某种形式的锁定管理以防止同时读写以及来自不同线程的写作。 并非如此。我传递的对象指针总是指向不同的对象,因此在处理过程中不需要锁定。 不,您共享对象的所有权,因为在发起线程中将有一个所有者,而在接收线程中将有另一个所有者。 没有。不同的对象=没有争用。生产者在推送消息并立即分配或分离另一个对象指针时放弃所有权。【参考方案5】:

这很简单(我很惊讶其他人写了这么长的回复!):

使用消息队列系统而不是“原始”共享数据意味着您只需在一个中心位置正确地进行一次同步(锁定/解锁资源)。

使用基于消息的系统,您可以从更高的角度思考“消息”,而不必再担心同步问题。无论如何,消息队列完全有可能在内部使用共享数据实现。

【讨论】:

【参考方案6】:

我使用了共享内存模型,其中指向共享内存的指针在消息队列中进行管理,并小心锁定。从某种意义上说,这是消息队列和共享内存的混合体。当必须在线程之间传递大量数据同时保持消息队列的安全性时,这种情况非常有用。

整个队列可以打包在一个带有适当锁定等的 C++ 类中。关键是队列拥有共享存储并负责锁定。生产者获取队列输入的锁并接收指向下一个可用存储块(通常是某种对象)的指针,填充并释放它。消费者将阻塞,直到生产者释放下一个共享对象。然后它可以获取存储的锁,处理数据并将其释放回池中。在一个适当设计的队列中,可以高效地执行多个生产者/多个消费者操作。考虑一个 Java 线程安全 (java.util.concurrent.BlockingQueue) 语义,但对于指向存储的指针。

【讨论】:

这正是我更喜欢用于批量数据处理的那种系统。唯一的锁是在推送/弹出指针时保护队列和池的短锁。没有线程对可以被另一个线程访问的数据进行操作,因此在处理缓冲区对象期间不会发生争用或锁定。【参考方案7】:

我有完全相同的问题。阅读答案后。我觉得:

    在最典型的用例中,队列 = 异步,共享内存(锁) = 同步。确实,你可以做一个异步版本的共享内存,但那是更多的代码,类似于重新发明消息传递***。

    更少的代码 = 更少的错误和更多的时间专注于其他事情。

优缺点前面的回答已经提到过,不再赘述。

【讨论】:

以上是关于消息队列在线程通信中比共享数据有啥优势?的主要内容,如果未能解决你的问题,请参考以下文章

用于在两个进程之间共享数据的本地消息队列

云计算openstack共享组件-消息队列rabbitmq

RTX线程通信之——消息队列

RTX线程通信之——消息队列

消息队列

Linux Program信号量共享内存和消息队列