Pull to refresh

Comments 65

А чем вас не устроил стандартный TThread?
TThread никуда не девается. TThread — это обертка над базовыми WinAPI функциями. Более того, на нижнем уровне библиотека OTL класс TThread успешно используется. Здесь же речь идет об уровнях гораздо более высоких по сравнению с TThread. Вы попробуйте сначала реализовать на основе голого TThread, например, конвейер (Pipeline) и тогда поймете, в чем разница.
Извините, промахнулся. Вопрос адресован ТС
Ну например когда я еще увлекался программизмом — тоже обожал Дельфи. Красивый язык, с которым не приходится изобретать велосипеды, но надо под каждый чих качать компоненты. Его идеологию ИМХО перенял C#.
Красивый язык, с которым не приходится изобретать велосипеды

Был в свое время, версии эдак до 6-й.

Его идеологию ИМХО перенял C#.

Внезапно, потому что их проектировал один и тот же человек. Собственно, примерно с момента как Хейльсберг ушел в Майкрософт, Дельфи и покатился по наклонной.
Embarcadero Delphi XE/XE2 качественные, удобные, современные продукты. «Дельфи покатился по наклонной» можно было говорить этак году в 2006-м, но уж точно не сейчас.
Его уникальность в том, что он одновременно позволяет создавать код сколь угодно высокого уровня, при этом оставаясь «близким к железу», т.к. на выходе мы получаем native-приложение, а не код для виртуальной машины Java или .Net.

Ни в Java, ни в .Net нет никаких виртуальных машин. Там есть JIT-компиляторы, которые работают не медленнее дельфовского RTTL.
Управляемое окружение.
коды на языке C#, например, компилируются в промежуточный байт-код (MSIL).
далее VM (CLR в данном случае) вызывает JIT-компилятор для компиляции MSIL в машинные инструкции.
CLR обеспечивает управление потоками, GC, JIT, Обработку исключений и еще много чего.

>>Ни в Java, ни в .Net нет никаких виртуальных машин.

пожалуйста, объясните это разработчикам Java Virtual Machine (JVM), а то они неправильно используют термины.

не надо путать VM для виртуализации и Process VM
blogs.msdn.com/b/brada/archive/2005/01/12/351958.aspx

Давайте сойдемся на том, что вопрос спорный и касается скорее понимания терминологии.

В Java когда-то была действительно полноценная виртуальной машина.
Точно, спасибо, совсем он выпал у меня из головы. Но в OmniThreadLibrary, безусловно, возможностей _гораздо_ больше.
Есть еще JEDI Core и JEDI VCL и там набор всевозможных компонентов JvThread, JvThreadTimer и т.д.
Еще раз акцентирую внимание читателей (видимо в статье не получилось поставить этот акцент), OmniThreadLibrary предоставляет гораздо более высокоуровневый подход к написанию многопоточного кода по сравнению с WinAPI и обертками типа TThread. Из приведенных выше примеров (какими бы примитивными они ни были) видно, что ни методов создания/уничтожения потоков, ни функций ожидания/синхронизации явным образом вызывать не нужно, все спрятано за очень красивыми и удобными абстракциями.
>>видно, что ни методов создания/уничтожения потоков, ни функций ожидания/синхронизации явным образом вызывать не нужно, все спрятано за очень красивыми и удобными абстракциями.

А есть примеры более сложных программ с использованием OmniThreadLibrary?

Интересует примеры:
1. Работа нескольких потоков с одним массивом данных.
2. Доступ и работа с БД из нескольких потоков.
3. Работа нескольких потоков по расчету каких-либо данных и вывод информации на форму, к примеру построение графиков.
>>1.Работа нескольких потоков с одним массивом данных.
Слишком общее описание вопроса, непонятно, какую именно задачу вы решаете.
>>2.Доступ и работа с БД из нескольких потоков.
Здесь все очень тонко. Если у вас один Connection, то вы сможете использовать его только в одном потоке. Если создавать Connection для каждого потока, который может обращаться к базе, то появляется риск нарваться на блокировки с самим собой не уровне СУБД.
>>3.
Опять же слишком общее описание.

OTL снабжена значительным количеством примеров, позволяющих быстро разобраться с основными идеями и начать применять библиотеку на практике.
>>OTL снабжена значительным количеством примеров, позволяющих быстро разобраться с основными идеями и начать применять библиотеку на практике.

В папке examples всего один пример: stringlist parser :(
Есть конечно много чего в папке tests, Вы про это говорите?
Да, именно про tests. Примеры, использующие высокоуровневые контрукции находятся ближе к концу списка. Лучше вообще эти примеры просматривать от конца к началу :)
Понятно, спасибо, буду изучать.
>И при этом язык Delphi очень прост и лаконичен, код на нем приятно читать и в нем достаточно легко разобраться, чего не могу сказать о коде на C или C++

Вот не скажи, одни только begin и end как код раздувают по размеру, да и синтаксис объявления функций весьма громоздкий. Для обучения хорошо, а тут только строчки лишние плодятся.
Ну и в плюсах тоже есть std::future в аналогичным функционалом. Да и в Qt уже столет в обед она была.
Вот не скажи, одни только begin и end как код раздувают по размеру, да и синтаксис объявления функций весьма громоздкий.

Это никак не противоречит «код на нем приятно читать и в нем достаточно легко разобраться».
Ага, читал. Отличная статья на момент ее написания. Когда попробовал OTL, понял что многими низкоуровневыми вещами для написания «первого многопоточного приложения» можно голову не забивать. OTL позволяет концентрироваться на полезном коде приложения, а не коде для поддержки запуска, остановки, уничтожения, синхронизации потоков.
Для первого и простого можно. Но если я напишу что-нибудь сложное с OTL, и в процессе тестирования вылезут гейзенбаги, то я бы хотел знать основы, чтобы хотя бы предположить, что пошло не так. Многопоточность не настолько простая область, имхо, чтобы совсем про нее не читать и довериться сторонним библиотекам.
Большое спасибо за статью!
Заинтересовало…

Присоединяюсь к просьбе рассмотреть пример: доступ и работа с БД из нескольких потоков.

А именно
1) с одной сессией
2) с несколькими сессиями.

Ваши рекомендации по этому поводу?

Заранее спасибо.
Извиняюсь, а в чем проблема работать с БД из нескольких потоков? Откройте несколько соединений к БД (т.е. каждый поток использует свое соединение) и работайте.

«Сессия» — имеется в виду прикладной объект с данными? Защитите его примитивным объектом синхронизации.
Если под сессией вы понимаете соединение (Connection), то с одним соединением из нескольких потоков работать не получится (разве что по очереди, что никакого смысла в рассматриваемом контексте не имеет), а с несколькими соединениями — технических ограничений нет. Но если у вас 2-х звенка и вы используете data-aware controls, то все попытки прикрутить сюда многопоточность — это в любом случае будет кривой костыль. Если у вас 3-х звенка, то тут уже могут быть варианты (зависит от архитектуры сервера приложений). Правда ИМХО при доступе к БД использовать многопоточность можно только для read-only запросов (SELECT) и только если согласованность данных, получаемых несколькими запросами, для вас значения не имеет, т.к. во всех остальных случаях (когда вам нужно из нескольких потоков изменять данные в БД или если согласованность данных, выбираемых несколькими запросами, для вас важна) использование многопоточности приведет к нежелательным и сложно отлавливаемым ошибкам и только запутает код.
Многопоточность при работе с БД удобно применять, когда какой-то процесс можно разделить на действия, не связанные с доступом к БД (например, чтение какой-то информации из файлов), и уже собственно на доступ к БД (запись информации из файлов в БД). Эти части можно выполнять независимо (например, используя многопоточный конвейер).
>Но если у вас 2-х звенка и вы используете data-aware controls, то все попытки >прикрутить сюда многопоточность — это в любом случае будет кривой костыль.

Я знал… я так и знал… :-)

>Эти части можно выполнять независимо (например, используя многопоточный конвейер)
А это как, простите?..
Имел в виду Pipeline из OmniThreadLibrary. Один этап конвейера читает файлы по одному, берет из них необходимую информацию и передает второму этапу. Второй этап выполняет запросы к БД. Этапы выполняются в параллельных потоках и никак не мешают друг другу, а ускорение налицо (чтение файлов происходит параллельно с выполнением запросов по обновлению данных в БД).
Признаться, рассказывать об основах многопоточной разработки в комментариях к статье довольно сложно :)
Простите, а что означает в строчке
vFuture: IOmniFuture;
тип integer в угловых скобках? :-) Или как это называется, и где про это почитать? :-)
Это назвается generics (generic types, дженерики или обобщенные типы). Появились они, если память мне не изменяет, в Delphi 2009.
Похоже на адаптацию .NET'ного Thread Pool'а и механизма асинхронного вызова делегатов Delegate.BeginInvoke, работающего на этом пуле, а так же от части WPF'ного Dispatcher'а. В C# 4.5 будет готовый синтаксический сахар для всех этих дел — ключевые слова async и wait.
Что касается чисто Delphi, многопоточность там приемлема, пока дело не доходит до взаимодействия с GUI. На моей практике в подавляющем большинстве случаев задача заключалась в необходимости запустить асинхронную операцию с выводом процента выполнения в GUI. А до GUI из фоновых потоков можно добраться только через одно место — очередь оконных сообщений основного потока (SendMessage, PostMessage), и через набор обработчиков этих сообщений с другой стороны. А всё по тому, что кое-кто не озаботился реализацией alertable message loop'а в VCL, что не позволяет производить «инъекции» своих методов обратного вызова в поток, обрабатывающий оконные сообщения через APC. А это могло бы существенно упростить жизнь.
>>Похоже на адаптацию .NET'ного Thread Pool'а
Это не адаптация. Это другая реализация похожих вещей (с нуля).

>>что не позволяет производить «инъекции» своих методов обратного вызова в поток
С помощью OTL такую инъекцию как раз можно произвести ;). Причем не только в главный поток, но и в любой другой. Если инъекция делается в главный поток, то он естественно должен находиться в активной петле обработки сообщений. Вот тут как раз об этом написано и также поясняется чем плох Synchronize. Мне удалось «эмулировать» Synchronize и при использовании неблокирующего Invoke, позволив таким образом вызывать пользовательские диалоги из дополнительных потоков (ну не красота ли?). Для этого пришлось написать такой метод:

// Глобальная крит. секция на на взаимодействие с пользователем (чтобы один диалог, сделав Application.ProcessMessages, не мог вызвать «из-под себя» другой диалог (инициированный другим потоком, но еще не активированный)
var
lParallelDialogCS: TCriticalSection;

procedure MainThreadOnly;
begin
Assert(GetCurrentThreadID = MainThreadID, 'Данный участок программы может выполняться только в основном потоке.');
end;

procedure RequestDialog(const aTask: IOmniTask;
aDialogProc: TOmniTaskInvokeFunction);
var
vCompleted: IOmniWaitableValue;
vExc: Exception;
begin
if not Assigned(aTask) then
begin
MainThreadOnly;
aDialogProc;
end
else begin
lParallelDialogCS.Enter;
try
vExc := nil;
vCompleted := CreateWaitableValue;
aTask.Invoke(
procedure
begin
try
aDialogProc;
except
vExc := AcquireExceptionObject;
end;
vCompleted.Signal;
end
);
vCompleted.WaitFor;
if Assigned(vExc) then
raise vExc;
finally
lParallelDialogCS.Leave;
end;
end;
end;

// Инициализация крит. секции осуществляется самим unit'ом
initialization
lParallelDialogCS := TCriticalSection.Create;

finalization
FreeAndNil(lParallelDialogCS);
Забыл пометить код как дельфийский. Пишу еще раз:

var
  lParallelDialogCS: TCriticalSection; // Критическая секция на взаимодействие с пользователем из параллельного потока

procedure MainThreadOnly;
begin
  Assert(GetCurrentThreadID = MainThreadID, 'Данный участок программы может выполняться только в основном потоке.');
end;

procedure RequestDialog(const aTask: IOmniTask;
  aDialogProc: TOmniTaskInvokeFunction);
var
  vCompleted: IOmniWaitableValue;
  vExc: Exception;
begin
  if not Assigned(aTask) then
  begin
    MainThreadOnly;
    aDialogProc;
  end
  else begin
    lParallelDialogCS.Enter;
    try
      vExc := nil;
      vCompleted := CreateWaitableValue;
      aTask.Invoke(
        procedure
        begin
          try
            aDialogProc;
          except
            vExc := AcquireExceptionObject;
          end;
          vCompleted.Signal;
        end
      );
      vCompleted.WaitFor;
      if Assigned(vExc) then
        raise vExc;
    finally
      lParallelDialogCS.Leave;
    end;
  end;
end;

initialization
  lParallelDialogCS := TCriticalSection.Create;

finalization
  FreeAndNil(lParallelDialogCS);

Ну с Invoke конечно веселее. Да, я от новостей о Delphi отстал, когда я в последний раз на ней писал, там еще не было анонимнах методов, обобщений и поддержки Unicode.
Чем плох Synchronize я там так и не увидел. И чем это лучше Synchronize тоже не понятно.
Мой RequestDialog ничем не лучше Synchonize, это как раз эмуляция Synchronize средствами OTL. А вот исходный метод Invoke лучше чем Synchronize тем, что позволяет вызвать из любого потока метод в контексте другого потока при этом не блокируя вызывающий поток. Вызов лишь «ставится в очередь» и вызывающий поток продолжает свою работу. Поток-адоресат выполнит метод в тот момент, когда прочитает из своей очереди сообщений соответствующее сообщение.
Это не лучше или хуже это просто по-другому. Иногда надо блокировать, иногда не надо. Есть же упомянутый Queue, который делает тоже самое.
Ну вот Invoke это примерно то же самое, что Queue, только в терминах фреймворка библиотеки OTL.
Насколько помню, в VCL все в порядке с message loop, просто нужно немного понимать, как он работает.

Тут уже упомянули про Synchronize() — это банально выполнение метода (процедуры) в основном потоке, перед переходом в idle. Дешево и сердито, но глючный поток может поломать всю программу.

Правильнее было бы создание простейшей глобальной thread-safe очереди сообщений на базе TThreadList, и обрабатывать ее по таймеру. Тогда код дочерних потоков будет полностью изолирован от кода основного потока, без лишних блокировок и заморочек. Даже если поток сломается или зависнет — прога просто поймает исключение в TApplicationEvents.OnException() и продолжит полет.
Да, в VCL c message loop действительно все в порядке. Имелась в виду ситуация
procedure A;
begin
  // Запускаем доп. поток
  // ... Что-то делаем ... В это время второй поток хочет сделать Synchronyze
  // Ждем завершения второго потока. Вот здесь надо делать ProcessMessages вручную, иначе можно ждать бесконечно
end;


Обработку сообщений по таймеру и «правильнее было бы» даже комментировать не буду.
Нежно любил Дельфю-7 за простой и читабельный синтаксис, удобные библиотеки и компоненты.
В новых версиях синтаксис испортился, при этом мало что улучшилось. Ну и хрен с ними.
А теперь открыл для себя Python, чего и вам желаю!
>В новых версиях синтаксис испортился
Это чем же он испортился интересно. Дженериками и анонимными методами? ;)
Оними самыми. Хотели как лучше, а получилось как у всех.
Не вижу причин для недовольства. Отличный функционал, ИМХО.
Функционал, безусловно, нужный. Но реализован как калька с С-образных языков, где вместо понятных человеку слов и символов — всякие галочки, точки, кавычки и прочие элементы таблицы ASCII, которые несут в себе особый внутренний смысл.
TObjectList вместо TObjectList это уже прорыв (кроме шуток)
На мой взгляд это уродливый костыль для обхода ограничений типизации.
Это не обход ограничений типизации, а наоборот, усиление типизации. Обычный TObjectList может хранить объекты любого (абсолютно любого) класса-наследника TObject. А «TObjectList угловая_скобка TPerson угловая_скобка» может хранить только объекты класса TPerson и его наследников, что позволяет не делать каждый раз преобразование «Items[i] as TPerson», а сразу обращаться к Items[i] как к TPerson.
А могло быть и так:

function total_age(x): integer;
var i: integer;
begin
  result=0;
  try
    for i=0 to x.count do result:=result+x[i].age;
  except
    print('че-то вы мне не то подсунули..');
  end;
end;


Без всяких этих извращений. Один хрен ведь типы заранее известны и в коде, и в runtime (RTTI), нафиг такие сложности с принудительной типизацией.

В свое время Борланд добавил кучу послаблений (отключаемых) для паскаля, и было всем удобно. Только компилятору было неудобно, но его мнения никто не спрашивал. А гадкий кодежир решил сделать удобно компилятору. Вот и пусть компилятор сам программы пишет.
За эти 6 лет мое мнение о типизации и Python несколько поменялось. Типизация нужна, и чем строже, тем лучше. Но безопасный способ ее обойти (без жесткого typecast) тоже бывает нужен. Что-то вроде variant с поддержкой классов, когда можно попытаться вызвать какой-то метод наугад, и это приведет не к segfault, а к цивильному исключению несовпадения типа.

Дженерики и анонимные методы я так ни разу и не использовал в Delphi. В Java и Kotlin так и не привык к анонимным процедурам, очень портят структуру кода.
Это изменение мнения по строгость типизации могу только приветствовать. Очень рад.

IDispatch для вариантов в Delphi давно поддерживается, но он сделан (и, кажется, до сих пор) без Code Insight, нормальной документации и без нормальных средств анализа и отладки, так что работать с ним очень болезненно ((

А на счёт дженериков и анонимных процедур — ИМХО, вопрос поддержки IDE, отладчиком и документацией тут тоже критичен. Но штуки вкусные, ИМХО.
Имел в виду «TObjectList угловая скобка T угловая скобка» вместо TObjectList (хабр вырезает их)
TObjectList<T> (пишите TObjectList&lt;T&gt;)

6 лет прошло! Лучше поздно, чем никогда :-)

Sign up to leave a comment.

Articles