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


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

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

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

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

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

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

7 6

7 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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