В чем преимущество очереди сообщений перед общими данными в потоке? - PullRequest
6 голосов
/ 19 августа 2011

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

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

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

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

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

Ответы [ 7 ]

7 голосов
/ 19 августа 2011

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

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

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

5 голосов
/ 19 августа 2011

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

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

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

2 голосов
/ 19 августа 2011

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

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

2 голосов
/ 19 августа 2011

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

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

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

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

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

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

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

1 голос
/ 19 августа 2011

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

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

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

1 голос
/ 19 августа 2011

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

0 голосов
/ 19 января 2013

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

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

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

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

...