Первый момент: все они являются объектами ядра, поэтому все они предполагают переключение из пользовательского режима в режим ядра. Это само по себе накладывает достаточно накладных расходов, поэтому вы вряд ли заметите реальную разницу между ними с точки зрения скорости или чего-то в этом роде. Следовательно, какой из них предпочтительнее, будет во многом зависеть от того, как вы структурируете данные в области общей памяти и как вы ее используете.
Начнем с, вероятно, самого простого случая: узким местом является область разделяемой памяти. Все время, пока потребитель не читает, производитель будет писать, и наоборот. По крайней мере, на первый взгляд кажется, что мы можем использовать один мьютекс. Производитель ожидает мьютекс, записывает данные, освобождает мьютекс. Потребитель ожидает мьютекс, считывает данные, освобождает мьютекс. Это продолжается до тех пор, пока все не будет сделано.
К сожалению, хотя это защищает от одновременного использования общего региона производителем и потребителем, оно не обеспечивает правильную работу. Например: производитель записывает буфер, полный информации, затем освобождает мьютекс. Затем он снова ожидает мьютекс, поэтому, когда считыватель закончит работу, он сможет записать больше данных, но в этот момент нет гарантии, что потребитель будет следующим, кто получит мьютекс. Производитель может немедленно вернуть их и записать больше данных поверх того, что он только что произвел, так что потребитель никогда не увидит предыдущие данные.
Один из способов предотвратить это — использовать пару событий: одно от производителя к потребителю, чтобы сообщить, что есть данные, ожидающие чтения, а другое — от потребителя к производителю, чтобы сообщить, что все данные в буфере были обработаны. читать. В этом случае производитель ожидает своего события, которое потребитель установит только после завершения чтения данных. Затем производитель записывает некоторые данные и сообщает событию потребителя, что некоторые данные готовы. Потребитель считывает данные, а затем сообщает о событии производителю, чтобы цикл мог продолжаться.
Пока у вас есть только один производитель и один потребитель, и вы рассматриваете все как один "фрагмент" данных, который контролируется вместе, этого достаточно. Однако это может привести к проблеме. Давайте рассмотрим, например, интерфейсную часть веб-сервера в качестве производителя и внутреннюю часть в качестве потребителя (и некоторый отдельный механизм для передачи результатов обратно на веб-сервер). Если буфер достаточно мал, чтобы вместить только один запрос, производителю, возможно, придется буферизовать несколько входящих запросов, пока потребитель обрабатывает один. Каждый раз, когда потребитель готов обработать запрос, производитель должен остановить свои действия, скопировать запрос в буфер и сообщить потребителю, что он может продолжить.
Однако основной смысл отдельных процессов состоит в том, чтобы позволить каждому из них действовать по собственному графику, насколько это возможно. Для этого мы можем освободить место в нашем общем буфере для ряда запросов. В любой момент некоторое количество этих слотов будет заполнено (или, если посмотреть на это с другой стороны, какое-то количество будет свободно). В этом случае нам как раз нужен счетный семафор для отслеживания этих слотов. Производитель может написать что-нибудь в любое время, когда хотя бы один слот свободен. Потребитель может что-то прочитать в любое время, когда хотя бы один слот заполнен.
Итог: выбор не в скорости. Речь идет о том, как вы используете/структурируете данные и доступ процессов к ним. Предполагая, что это действительно так просто, как вы описываете, пара событий, вероятно, является самым простым механизмом, который будет работать.
person
Jerry Coffin
schedule
21.11.2012