в чем преимущество очереди сообщений перед общими данными в потоковой связи?

Я прочитал статью о многопоточном дизайне программ http://drdobbs.com/architecture-and-design/215900465, он говорит, что это лучшая практика, что " замена общих данных асинхронными сообщениями. Насколько это возможно, предпочитайте держать данные каждого потока изолированными (неразделенными), и пусть потоки вместо этого общаются через асинхронные сообщения, которые передают копии данных".

что меня смущает, так это то, что я не вижу разницы между использованием общих данных и очередей сообщений. Я теперь я работаю над проектом без gui в windows, поэтому давайте использовать очереди сообщений windows. и возьмем в качестве примера традиционную проблему "производитель-потребитель".

используя общие данные, будет общий контейнер и замок, защищающий контейнер между потоком производителя и потоком потребителя. когда продукт выхода производителя, он сперва ждет замок и после этого пишет что-то к контейнеру после этого выпускает замок.

используя очередь сообщений, производитель может просто PostThreadMessage без блока. и это преимущество асинхронного сообщения. но я думаю, что должна существовать какая-то блокировка, защищающая очередь сообщений между двумя потоками, иначе данные определенно будут повреждены. вызов PostThreadMessage просто скрывает детали. Я не знаю, правильно ли мое предположение, но если это правда, преимущество, кажется, больше не существует,так как оба метода делают то же самое, и единственная разница в том, что система скрывает детали при использовании сообщения очереди.

ps. возможно, очередь сообщений использует неблокирующий containner, но я также могу использовать параллельный контейнер. Я хочу знать, как реализуется очередь сообщений и есть ли разница в производительности между двумя способами?

обновление: Я все еще не понимаю концепцию асинхронного сообщения, если операции очереди сообщений все еще заблокированы где-то еще. Поправьте меня, если моя догадка была неправильной: когда мы используем общие контейнеры и замки, мы заблокируем их нитка. но при использовании очередей сообщений поток меня немедленно возвращался и оставлял блокирующую работу какому-то системному потоку.

7 ответов


сообщение передает полезно для обмена меньшими объемами данных, потому что не нужно избегать конфликтов. Это намного проще реализовать, чем общая память для межкомпьютерной связи. Кроме того, как вы уже заметили, передача сообщений имеет то преимущество, что разработчикам приложений не нужно беспокоиться о деталях защиты, таких как общая память.

общий позволяет максимальной скорости и удобству связи, как это можно сделать на скоростях памяти, когда внутри компьютера. Общая память обычно быстрее, чем передача сообщений, как передача сообщений обычно реализуется с помощью системных вызовов и, следовательно, требует более трудоемких задач вмешательства ядра. Напротив, в системах с общей памятью системные вызовы требуются только для установления областей общей памяти. Как только установленный, весь доступ обработан как нормальные обращения к памяти без дополнительной помощи от ядро.

редактировать: один случай, который вы, возможно, захотите реализовать свою собственную очередь, заключается в том, что есть много сообщений, которые будут производиться и потребляться, например, система ведения журнала. С implemenetation из PostThreadMessage, его пропускная способность очереди является фиксированной. Сообщения лики заблудиться, если что объем.


представьте,что у вас есть 1 поток, производящий данные, и 4 потока, обрабатывающие эти данные (предположительно, для использования многоядерной машины). Если у вас есть большой глобальный пул данных, вам, вероятно, придется заблокировать его, когда любой из потоков требуется доступ, потенциально блокируя 3 других потока. По мере добавления дополнительных потоков обработки вы увеличиваете вероятность того, что блокировке придется ждать и увеличить, сколько вещей, возможно, придется ждать. В конечном итоге добавление большего количества потоков ничего не дает потому что все, что вы делаете, это тратите больше времени на блокировку.

Если вместо этого у вас есть один поток, отправляющий сообщения в очереди сообщений, по одному для каждого потока потребителя, то они не могут блокировать друг друга. Вы все еще должны заблокировать очередь между потоками производителя и потребителя, но поскольку у вас есть отдельная очередь для каждого потока, у вас есть отдельная блокировка, и каждый поток не может блокировать все остальные, ожидающие данных.

Если вы вдруг получите 32-ядерную машину, вы можете добавить еще 20 обработки потоки (и очереди) и ожидают, что производительность будет масштабироваться довольно линейно, в отличие от первого случая, когда новые потоки будут просто сталкиваться друг с другом все время.


Я использовал модель общей памяти, где указатели на общую память управляются в очереди сообщений с тщательной блокировкой. В некотором смысле, это гибрид между очередью сообщений и общей памяти. Это очень важно, когда большое количество данных должно передаваться между потоками при сохранении безопасности очереди сообщений.

вся очередь может быть упакована в один класс C++ с соответствующей блокировкой и тому подобное. Ключ заключается в том, что очередь владеет общим хранилищем и позаботится о замке. Производители получают блокировку для ввода в очередь и получают указатель на следующий доступный фрагмент хранилища (обычно какой-то объект), заполняет его и освобождает. Потребитель будет блокировать, пока следующий общий объект не будет выпущен производителем. Затем он может получить блокировку хранилища, обработать данные и освободить их обратно в пул. В подходящей очереди можно выполнять несколько операций производителя / потребителя с большой эффективностью. Думаю, что Java потокобезопасный (java.утиль.параллельный.BlockingQueue) семантика, но для указателей на хранилище.


конечно, есть "общие данные", когда вы передаете сообщения. Ведь само сообщение является неким данным. Однако важным отличием является то, что когда вы передаете сообщение, потребитель получит скопировать.

вызов PostThreadMessage просто скрыть детали

Да, это так, но будучи вызовом WINAPI, вы можете быть уверены, что он делает это правильно.

Я все еще не понимаю концепцию асинхронных сообщений если операции очереди сообщений по-прежнему заблокированы в другом месте.

на преимущество больше безопасности. У вас есть механизм блокировки, который систематически применяется при передаче сообщения. Вам даже не нужно думать об этом, вы не можете забыть запереть. Учитывая, что многопоточные ошибки являются одними из самых неприятных (подумайте об условиях гонки), это очень важно. Передача сообщений-это более высокий уровень абстракции, построенный на блокировках.

в минус это то, что передача больших объемов данных будет, вероятно, медленной. В этом случае, вы должны использовать общую память.

для передачи состояния (т. е. рабочего потока, сообщающего о прогрессе в GUI)сообщения-это путь.


Это довольно просто (я поражен, что другие написали такие длинные ответы!):

использование системы очереди сообщений вместо "необработанных" общих данных означает, что вы должны получить синхронизацию (блокировку/разблокировку ресурсов) только один раз, в центральном месте.

с системой на основе сообщений вы можете думать в более высоких терминах "сообщений", не беспокоясь о проблемах синхронизации больше. Для чего это стоит, вполне возможно, что очередь сообщений реализуется через общие данные.


Я думаю, что это ключевая часть информации: "насколько это возможно, предпочитайте держать данные каждого потока изолированными (неразделенными), и пусть потоки вместо этого общаются через асинхронные сообщения, которые передают копии данных". Т. е. используйте производитель-потребитель:)
Вы можете сделать свое собственное сообщение или использовать что-то, предоставленное ОС. Это деталь реализации (необходимо сделать правильно ofc). Ключ состоит в том, чтобы избежать общих данных, как в том, что одна и та же область памяти изменяется несколькими потоками. Это может привести к трудно найти ошибки, и даже если код совершенен он будет есть производительность из-за всех блокировок.


У меня был точно такой же вопрос. После прочтения ответов. Я чувствую:

  1. в большинстве случаев использования queue = async, shared memory (locks) = sync. Действительно, Вы можете сделать асинхронную версию общей памяти, но это больше кода, аналогично изобретению колеса передачи сообщений.

  2. меньше кода = меньше ошибок и больше времени, чтобы сосредоточиться на других вещах.

плюсы и минусы уже упоминались в предыдущих ответах, поэтому я буду не повторять.