2017-01-04 4 views
0

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

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

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

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

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

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

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

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

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

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

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

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

Ниже приведен пример шаблона производителя/потребительского образца, написанного в Аде.

------------------------------------------------------------------ 
-- Sampling Consumer -- 
------------------------------------------------------------------ 
with Ada.Text_IO; use Ada.Text_IO; 

procedure Sampling_PC is 
    protected Buf is 
     procedure Write(Item : in Integer); 
     function Read return Integer; 
     procedure Set_Done; 
     function Get_Done return Boolean; 
    private 
     Value : Integer := Integer'First; 
     Is_Done : Boolean := False; 
    end Buf; 

    protected body Buf is 
     procedure Write(Item : in Integer) is 
     begin 
     Value := Item; 
     end Write; 
     function Read return Integer is 
     begin 
     return Value; 
     end Read; 
     procedure Set_Done is 
     begin 
     Is_Done := True; 
     end Set_Done; 
     function Get_Done return Boolean is 
     begin 
     return Is_Done; 
     end Get_Done; 
    end Buf; 

    task Consumer; 

    task body Consumer is 
    begin 
     while not Buf.Get_Done loop 
     Put_Line("Consumer read" & Integer'Image(Buf.Read)); 
     end loop; 
    end Consumer; 

begin 
    for I in 1..10 loop 
     Put_Line("Producer writing" & Integer'Image(I)); 
     Buf.Write(I); 
    end loop; 
    Buf.Set_Done; 
end Sampling_PC; 

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

В приведенном выше примере используются только процедуры и функции. Задача «Потребитель» считывает защищенный объект с помощью функций, в то время как производитель, который является основной задачей программы, в этом случае записывает защищенный объект с помощью процедур.

Пример шаблона производитель-потребитель, используя множество производителей и множество потребителей:

------------------------------------------------------------------ 
-- Multiple producers and consumers sharing the same buffer -- 
------------------------------------------------------------------ 
with Ada.Text_IO; use Ada.Text_Io; 

procedure N_Prod_Con is 
    protected Buffer is 
     Entry Write(Item : in Integer); 
     Entry Read(Item : Out Integer); 
    private 
     Value : Integer := Integer'Last; 
     Is_New : Boolean := False; 
    end Buffer; 
    protected body Buffer is 
     Entry Write(Item : in Integer) when not Is_New is 
     begin 
     Value := Item; 
     Is_New := True; 
     end Write; 
     Entry Read(Item : out Integer) when Is_New is 
     begin 
     Item := Value; 
     Is_New := False; 
     end Read; 
    end Buffer; 

    task type Producers(Id : Positive) is 
     Entry Stop; 
    end Producers; 
    task body Producers is 
     Num : Positive := 1; 
    begin 
     loop 
     select 
      accept Stop; 
      exit; 
     or 
      delay 0.0001; 
     end select; 
     Put_Line("Producer" & Integer'Image(Id) & " writing" & Integer'Image(Num)); 
     Buffer.Write(Num); 
     Num := Num + 1; 
     end loop; 
    end Producers; 

    task type Consumers(Id : Positive) is 
     Entry Stop; 
    end Consumers; 

    task body Consumers is 
     Num : Integer; 
    begin 
     loop 
     select 
      accept stop; 
      exit; 
     or 
      delay 0.0001; 
     end select; 
     Buffer.Read(Num); 
     Put_Line("Consumer" & Integer'Image(ID) & " read" & Integer'Image(Num)); 
     end loop; 
    end Consumers; 
    P1 : Producers(1); 
    P2 : Producers(2); 
    P3 : Producers(3); 
    C1 : Consumers(1); 
    C2 : Consumers(2); 
    C3 : Consumers(3); 
begin 
    delay 0.2; 
    P1.Stop; 
    P2.Stop; 
    P3.Stop; 
    C1.Stop; 
    C2.Stop; 
    C3.Stop; 
end N_Prod_Con; 
+0

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

+0

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

+0

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

ответ

2

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

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

Как только мы расслабляем все эти предположения, наличие очереди может иметь смысл. Среди прочего, это позволяет системе грациозно обрабатывать временные всплески спроса.

Это не означает, что очередь на единицу емкости (блокировка или перезапись) не имеет использования. В некоторых случаях это именно то, что требуется.

+0

Даже концепция балансировки нагрузки имеет некоторые ограничения. Например, если веб-сервер имеет дело с обработкой заказа, он должен установить выделенное соединение между конечным пользователем и конкретной корзиной покупок в базе данных. Конечный пользователь не будет вводить данные по обычной ставке, но в конце базы данных будет выделен выделенный потребитель, ожидающий событий корзины покупок. Такая система может использовать пулы потоков, но нет никакой пользы в использовании очереди между клиентом и базой данных. –

+0

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