Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Пул потоков.


Страницы:   1 2 3 4 5 6

panov ©   (2004-10-14 14:34:00) 

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


unit uTThreadPool;

interface

uses
 Classes, windows, messages;

var
 WM_EndThreadExecuter: DWORD;      //Сообщение об окончании обработки
 WM_AddThread: DWORD;              //Сообщение о добавлении элемента в очередь

type
//Тип процедуры для выполнения в отдельном потоке
 TThreadProc=procedure(aParm: pointer);

//Структура для передачи параметров
 PQueueRec=^TQueueRec;
 TQueueRec=record
   rProc: TThreadProc;
   rParm: Pointer;
 end;

//Элемент пула потоков
 TThreadExecuter=class(TThread)
 private
   FThreadIdOwner: THandle;        //Родительский поток TThreadPool
   FThreadFree: Boolean;           //Поток свободен/занят работой
   FExecProc: TThreadProc;         //Адрес процуедуры для выполнения
   FParm: Pointer;                 //Указатель на параметр,
                                   //Предаваемый в процедуру FExecProc
   FNumThread: Integer;            //Нмер потока в пуле
 protected
   procedure Execute; override;
 public
//В конструктор передается ThreadId родительского потока TThreadPool
   constructor Create(const aOwnerThread: THandle;const aNumThread: Integer);
 end;

//Менеджер пула потоков TThreadExecuter
 TThreadPool = class(TThread)
 private
{ TODO -cВажно :
Добавить код для проверки работоспособности потоков из пула
Зависшие убивать по таймауту }
   FSemaphore: THandle;            //Семафор-счетчик потоков
   FMaxThreads: Integer;           //Максимальное число потоков в пуле
   FListQueue: TList;              //Очередь заданий на выполнение
   FPool: array of TThreadExecuter; //Пул потоков
   procedure JobAdd(const aProc: TThreadProc;const aParm: Pointer); //Добавление
                                                                    //заданий в очередь
   function StartThreadFromQueue: Boolean; //Передача задания из очереди в пул
                                           //на выполнение
 protected
   procedure Execute; override;
 public
//В конструктор передается максимальное число потоков
   constructor Create(aMaxThreads: Integer);
   destructor Destroy;override;
//Добавление заданий из внешних потоков в очередь потоков
//Параметры:
//  aProc - адрес процедуры для выполнения
//  aParm - адрес памяти, по которуму нахлодятся параметры
//          для выполнения прцедуры
   procedure AddThread(aProc:TThreadProc;aParm:Pointer);
 end;

implementation

uses Unit1;

{ TThreadExecuter }

constructor TThreadExecuter.Create(const aOwnerThread: THandle;const aNumThread: Integer);
begin
 inherited Create(True);
 FreeOnTerminate := True;
 FThreadFree := True;
 FThreadIdOwner := aOwnerThread;
 FNumThread := aNumThread;
 Resume;
end;

procedure TThreadExecuter.Execute;
begin
 while not Terminated do
 begin
   Suspend;                        //Засыпаем сразу
   try
     if Assigned(FExecProc) then FExecProc(FParm); //Выполнение переданной
                                                   //процедуры
   except
   end;
//Передача в родительский поток сообщения о завершении обработки
   PostThreadMessage(FThreadIdOwner,WM_EndThreadExecuter,FNumThread,0);
   if Terminated then break;
 end;
end;


panov   (2004-10-15 19:32:00) [21]

>Суслик ©   (15.10.04 18:45) [90]

Так как желание продолжить обсуждение есть, то могу выложить здесь черноввые наброски своего класса(panov ©   (14.10.04 19:04) [62]), а также модуль сс своей реализацией потока от y-soft(если будет получено разрешение от него).


Суслик   (2004-10-15 18:45:00) [22]

А впрочем, не хотите, как хотите. Полезная школа для неновичков была бы.


Суслик   (2004-10-15 18:44:00) [23]


>  [88] Игорь Шевченко ©   (15.10.04 18:34)

Сервер (компьютераная экономическая игра). Многопоточный. Но она реализована очень слабо - маленькая нагрузка, поэтому пулами не пользуемся. А можно было бы.


> Кстати, в Win2k он уже встроен в систему.

Тот же Фаулер пишет (арх. корп. прогр. прил.), что есть команды разработчков, которые предпочитают не пользоваться готовыми решениями. Я из их группы. Также он пишет, что по определенным соображениям такие разработчики имеют право на существование. Логика ясна?


Игорь Шевченко   (2004-10-15 18:34:00) [24]


> Пул потоков нельзя отнести в фак, но в тоже время он является
> полезным и частоприменимым (в определенных областях) классом


Например, в каких областях ?

Кстати, в Win2k он уже встроен в систему.


Суслик   (2004-10-15 18:06:00) [25]

Прошу прощения, за реанимирование темы.

Но я бы хотел высказать пожелание автору топика.

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

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

Другой вопрос, что существуют проблемы, которые имеют определенное название. Например, пул потоков. Пул потоков нельзя отнести в фак, но в тоже время он является полезным и частоприменимым (в определенных областях) классом.

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

Поэтому предложение автору сего топика продолжить обсуждение кода пула потоков.


Игорь Шевченко   (2004-10-15 13:04:00) [26]

Суслик ©   (15.10.04 12:55) [85]

Вот теперь и мы пришли к единому мнению :)


> Есть какие-нибудь книги про всякие приемы многопоточного
> программирования?


Рихтер, как ни странно, программирование для Win32.
У Таненбаума очень хорошо рассказано про концепции.

Я просто с понятием "
> это гарантированное предоставление монопольного доступа
> к ресурсу одному процессу в каждый момент времени непротиворечивого
> состояния ресурса.
"
знаком с 1985 года, если не ошибаюсь, первый раз встретил у Шоу, в "Логическом проектировании операционных систем".


Суслик   (2004-10-15 12:55:00) [27]


>  [84] Игорь Шевченко ©   (15.10.04 12:50)

если учитывать такое определение

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

то  полность согласен с этим

> В коде, приведенном тобой, при помощи специального алгоритма,
> гарантируется непротиворечивое состояние ресурса для каждого
> потока в любой момент времени.


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

ЗЫ. Тонкое это дело. Есть какие-нибудь книги про всякие приемы многопоточного программирования?


Игорь Шевченко   (2004-10-15 12:50:00) [28]

Суслик ©   (15.10.04 12:43) [83]

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

Слово процесс для Windows заменяется на поток.

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


Суслик   (2004-10-15 12:43:00) [29]


>  [81] Игорь Шевченко ©   (15.10.04 12:36)

не понял:(


> совершаются с неизменным сдвигом по фазе друг относительно
> друга или одновременно.


Т.е. это значит, что синхронизируемый кусок кода должен выполняться в каждый момент не более чем одним потоком? Или нет?


panov   (2004-10-15 12:42:00) [30]

>Zelius ©   (15.10.04 11:36) [76]
Что бы не было вопрос при создании потомков TThread о том что раньше отработает, конструктор с инициализацией всех ресурсов или начнет Execute выполняться, я всегда сначала инициализирую ресурсы и только потом вызываю наследуемый конструктор:

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


Игорь Шевченко   (2004-10-15 12:36:00) [31]

Суслик ©   (15.10.04 12:33) [80]

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

http://encycl.yandex.ru/cgi-bin/art.pl?art=bse/00071/31300.htm&encpage=bse&mrkp=/yandbtm7%3Fq%3D-811200565%26p%3D0%26g%3D0%26d%3D0%26ag%3Denc_abc%26tg%3D1%26p0%3D0%26q0%3D1334144256%26d0%3D0%26script%3D/yandpage%253F

Мог бы и сам посмотреть (с)


Суслик   (2004-10-15 12:33:00) [32]


>  [79] Игорь Шевченко ©   (15.10.04 12:29)

Определение синхронизации в студию!


Игорь Шевченко   (2004-10-15 12:29:00) [33]

Суслик ©   (15.10.04 12:18) [78]

В этом коде модификация списка, которая может затронуть другие потоки, выполняется одной функцией InterlockedExchange. Строчка, которая тебя поразила - это искусственное действие, направленное на то, чтобы модификацию можно было выполнить той самой единственной функцией.
InterlockedExchange реализуется единственной командой (не считая подготовки аргументов) lock cmpxchg, которая в данном случае успешно может использоваться для модификации, так как является атомарной (не прерываемой в середине исполнения) и атомарной же на многопроцессорной системе, так как префикс lock выдает сигнал на блокировку шины доступа к памяти.

Точно такие же приемы используются при работе с длинными строками.

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


Суслик   (2004-10-15 12:18:00) [34]

2Игорь Шевченко

> Синхронизация потоков невозможна без диспетчеризации, согласись.
> То есть, как минимум, одни поток должен уметь сказать WaitForSingleObject
> (или какую-то разновидность), пока другой поток не освободит
> ресурс. После того, как ресурс освобожден, об этом должно
> стать известно диспетчеру, чтобы он смог запустить ожидавший
> поток.
>
> Разумно ?

Да разумно. Но WaitForSingleObject не единственное решение.

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

Класс TThreadLocalCounter (модуль sysutils) реализует TLS (не полноценный, а для одного конкретного целочисленного значения: TThreadInfo.RecursionCount), средствами дельфи. Причем существенно быстрее, чем TLS windows.
Идея такая: есть список (fHashTable) голов однонапрасленных списков. Хэш индекс строится исходя из GetCurrentThreadId.
Приведенный метод занимается тем, что выделяет вызвавшему потоку структуру PThreadInfo. Поиск идет либо в списке свободных структур (если поток отказывается от использования структура помечается свободной, но из списка не удаляется), либо если не нашел, то создает новую. Заметь, тут нет никаких критических секций. Внимательно читай комментарий. Собственно в тех двух строчках, которые о коментриует, и есть вся суть метода и моего утвержднеия о возможности синхронизации без ОЯ и даже без КС.

procedure TThreadLocalCounter.Open(var Thread: PThreadInfo);
var
 P: PThreadInfo;
 H: Byte;
 CurThread: Cardinal;
begin
 InterlockedIncrement(FOpenCount);
 H := HashIndex;
 P := FHashTable[H];
 CurThread := GetCurrentThreadID;

 // КАЖДОМУ ПОТОКУ ВЫДЕЛЯТЕСЯ РОВНО ОДНА СТРУКТУРА (ЭТО ЕЩЕ ОДНО ОТЛИЧИЕ ОТ TLS WINDOWS)
 while (P <> nil) and (P.ThreadID <> CurThread) do
   P := P.Next;

 // ЕСЛИ НЕ НАШЛИ, ТО...
 if P = nil then
 begin
   // ПЫТАЕМСЯ НАЙТИ В НЕИСПОЛЬЗУЕМЫХ СТРУКТРУАХ СВОБОДНУЮ
   P := Recycle;

   // ЕСЛИ ТАКОВЫХ НЕТ, ТО СОЗДАЕМ НОВУЮ
   if P = nil then
     P := PThreadInfo(AllocMem(sizeof(TThreadInfo)));

   P.ThreadID := CurThread;

   // А ВОТ ТЕПЕРЬ ЕЕ НУЖНО ВСТАВИТЬ В СПИСОК. ЧИТАЙ КОМЕНТАРИЙ.

   // Another thread could start traversing the list between when we set the
   // head to P and when we assign to P.Next.  Initializing P.Next to point
   // to itself will make others loop until we assign the tail to P.Next.
   P.Next := P; ????? ВОТ ЭТО МЕСТО МЕНЯ ПОТРЯСЛО!
   P.Next := PThreadInfo(InterlockedExchange(Integer(FHashTable[H]), Integer(P)));
 end;
 Thread := P;
end;


После этого продолжим обсуждение синхронизацию доступа к общему ресурсу без ОЯ и КС.


Erik1   (2004-10-15 11:56:00) [35]

Мастера фигей маются, как новички. Видать и им ничто человеческое нечуждо. :)
 Вобщето надо проежде всего позаботится об понятности алгоритма, его эфективновность оченивается вовсе не в количестве обращений к ядру. Вобщее правило: если алгоритм красив, то он ненуждается в улучшениях!
Суслик [71]
 В 99% случаев я бы выбрал второй вариант, проше читать. А эфективность можно уличшить как нибудь по другому.
to panov
У меня все потоки используют WaitForMultipleObjects с двумя event как минимум. Первый говорит потоку начать работу, второй терминировать поток. Это неокажет некакого замедления на расчеты, поскольку они выполняются внутри процедувы. А если расчеты очень короткие но частые, то suspend resume замедлят их еще больше. К томуже в Dlephi выще 5 версии нельзя делать resume 2 раза подряд, будет exception.


Zelius   (2004-10-15 11:36:00) [36]

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

constructor TThreadExecuter.Create(const aOwnerThread: THandle;const aNumThread: Integer);
begin
FreeOnTerminate := True;
FThreadFree := True;
FThreadIdOwner := aOwnerThread;
FNumThread := aNumThread;
inherited Create(True);
end;


Игорь Шевченко   (2004-10-15 11:18:00) [37]

Суслик ©   (14.10.04 19:08) [64]

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

Разумно ?

Спин-блокировка применима для синхронизации между потоками в многопроцессорной системе, когда поток на одном процессоре входит в цикл (крутится - spin), опрашивая общую переменную.


Суслик   (2004-10-15 11:12:00) [38]


> Ой ли? Разве этот код эквивалентен

нет, но делает тоже самое (по сути)


Verg   (2004-10-14 22:43:00) [39]


> Как вы думаете, почему не написано так (мне какжется так
> наглядней)
>
>    if C < 0 then L := I + 1 else
>    if C > 0 then   H := I - 1(* C>0 *)else
>    if C = 0 then
>    begin
>      Result := True;
>      if Duplicates <> dupAccept then L := I;
>    end;


Ой ли? Разве этот код эквивалентен


>    if C < 0 then L := I + 1 else
>    begin
>       H := I - 1;// C>=0
>      if C = 0 then
>      begin
>        Result := True;
>        if Duplicates <> dupAccept then L := I;
>      end;
>    end;


??


VMcL   (2004-10-14 21:13:00) [40]

> Как вы думаете, почему не написано так (мне какжется так наглядней)

  if C < 0 then L := I + 1 else
 if C > 0 then  H := I - 1 else
 if C = 0 then
 begin
  Result := True;
  if Duplicates <> dupAccept then L := I;
 end;


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

if C < 0 then L := I + 1 else
if C > 0 then  H := I - 1 else
//if C = 0 then
begin
  Result := True;
  if Duplicates <> dupAccept then L := I;
end;


Страницы:   1 2 3 4 5 6

Наверх

Титульная страница Поиск, карта сайта Написать письмо