среда, 22 декабря 2010 г.

Использование OmniThread Libray 2.0 (OTL 2.0) для создания многопоточных приложений - 1


Недавно вышла OTL 2.0, в коей были сделаны многие изменения и добавлены новые возможности. О них я и постараюсь в мере сил рассказать, опираясь на материал предоставленный автором библиотеки.
Итак, как мы помним, библиотека OTL разрабатывалась автором, как библиотека, призванная существенно облегчить жизнь Delphi- программистам при написании многопоточных приложений (путем предоставления оным классов и примитивов, облегчающих разработку). В частности версия OTL 1.05 была именно заточена под эти требования. В версии же 2.0 автор пошел далее, и основной упор сделал на высокоуровневое программирование параллельных вычислений и фьючерсов (futures)
Мое примечание: К сожалению, пока я не совсем понял что имел автор под словом фючерс (futures) касательно к OTL и решил, что раз автор употребляет такое наименование то и я не буду отходить далеко от него.
Стоит сразу обратить внимание коллег, на то, что новые возможности будут работать только в версиях Delphi 2009  и выше, так как их реализация сильно привязана к новым возможностям языка, таким как анонимные методы, обобщенное программирование и к некоторым так сказать недокументированным возможностям.
Итак, вкратце, рассмотрим параллельное программирования для OTL на основе OTL 2.0. Что такое параллельное программирование? Вкратце это можно охарактеризовать так, что некий процесс вычислений (работы) выполняется не в одном потоке (процессоре) а по возможности задействует все доступные процессоры и нити. Что как вы понимаете, существенно ускоряет например, некоторые расчеты и обработки.
Так вот OTL позволяет некоторый код, который нужно распараллелить вывести в различные параллельные примитивы OTL. Параллельные примитивы – это такие механизмы OTL позволяющие некоторые типовые конструкции языка обрабатывать параллельно. Например, наглядным примером распараллеливания является так называемый “параллельный цикл”.  Примитив в OTL - Parallel.ForEach.
Данный параллельный цикл существовал и в OTL 1.05 но носил скорее всего экспериментальный характер. В версии же 2.0 автор координально переписал  API (в частности фронтальную часть) данного механизма.
Запись логики для цикла Parallel.ForEach похожа на запись последовательного цикла. Нет необходимости создавать потоки или очередь рабочих элементов. В основных циклах нет необходимости принимать блокировки. В следующем примере кода показан параллельный эквивалент обычного цикла - Parallel.ForEach, который демонстрирует добавление 1000 элементов в коллекцию

  Parallel.ForEach(1, 1000).Execute(
    procedure (const elem: integer)
    begin
        outQueue.Add(elem);
    end);

Стоит отметить, что базовый ForEach(from,to: integer) изменился не сильно. Единственное различие - то, что тип параметра Execute - теперь "целое число" а не тип TOmniValue
Следующий пример, предоставленный автором, показывает простоту работы Parallel.ForEach. Следующий код будет выполнен параллельно на всех возможных ядрах.

На заметку: Если у Вас есть данные в контейнере, который поддерживает перечисление (с одним ограничением - счётчик должен быть осуществлен как класс, не как интерфейс или запись), тогда, Вы можете перебрать этот контейнер параллельно.
Пример
  var
    nodeList := TList.Create;

  Parallel.ForEach<integer>(nodeList).Execute
       (  // анонимный метод, код передаем на выполнение
       procedure (const elem: integer)
       begin
           // outQueue это TOmniBlockingCollection, который позволяет
           // вызывать  метод Add, из многократных потоков одновременно.
          outQueue.Add(elem);
       end
       );

ForEach также позволяет параллельным потокам быть выполненными асинхронно, что мы увидим на следующем примере:
Пример
Var
  // Счетчик
  prime     : TOmniValue;
  // Интерфейс объекта представляющего блокирующую коллекцию
  // блокирующая коллекция вкратце представляет собой обычную коллекцию, но
  // реализованную потокозащищенной, то есть работа с элементами коллекции может
  // вестись из различных потоков без риска рассинхронизации
  primeQueue: IOmniBlockingCollection;
begin
  lbLog.Clear;
  primeQueue := TOmniBlockingCollection.Create;
  // распараллеливаем цикл
  Parallel.ForEach(1, 1000).NoWait
    .OnStop( 
      // Стоп, окончание  
      procedure
      begin
        primeQueue.CompleteAdding;
      end)
    .Execute(
      // выполнение
      procedure (const value: integer)
      begin
           primeQueue.Add(value); // добавляем в очередь многопоточно
      end
           );

  // Читаем числа из этой очереди и показываем их на экране
  for prime in primeQueue do begin  // пробегаемся по коллекции
    lbLog.Items.Add(IntToStr(prime));
    lbLog.Update;
  end;
end;

Продолжение следует...

2 комментария:

  1. На первый взгляд - человек переносит дотнетовскую Task Parallel Library на дельфи.

    ОтветитьУдалить
  2. jack128

    Вы совершенно правы, многое взято оттуда, да и сам автор это не скрывает, что некоторые вещи он пишет под влиянием этой библиотеки

    ОтветитьУдалить