![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Пул потоков.
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;
Суслик (2004-10-14 16:07:00) [81]И вообще - на фига тут FSemaphore: он же используется в контексте одного потока?
Суслик (2004-10-14 16:03:00) [82]
> [28] panov © (14.10.04 16:01)
предлагаю следующее.
1. Учесть те замечания в которых я оказался прав.
2. Реализовать синхронизацию в целях обеспечения своевременности создания очереди.
3. Выложить все в полном виде.
Тогда продолжим разговор. Ок?
panov (2004-10-14 16:01:00) [83]>Суслик © (14.10.04 15:58) [26]
С чего это? TThreadExecuter уже создан. С чего ему не успевать что-то делать.
Как это с чего?
Пул создается не весь сразу, а по необходимости до достижения максимального количества.
Суслик (2004-10-14 16:01:00) [84]
> [27] panov © (14.10.04 15:59)
> Функция StartThreadFromQueue должна возвращать True в единственном
> случае - при передаче задания на выполнение.
> В остальных случаях возвращается False.
рассмотрел :)) Виноват:)
panov (2004-10-14 15:59:00) [85]>Суслик © (14.10.04 15:55) [25]
Функция StartThreadFromQueue должна возвращать True в единственном случае - при передаче задания на выполнение.
В остальных случаях возвращается False.
Суслик (2004-10-14 15:58:00) [86]
> Та же самая ситуация возникает, что и с TThreadPoool: поток
> не успевает начать выполнять метод TThreadExecuter.Execute
С чего это? TThreadExecuter уже создан. С чего ему не успевать что-то делать.
> А какая разница, сколько раз Resume вызывать?
Согласен, что в данном случае никакой.
Суслик (2004-10-14 15:55:00) [87]
> [23] panov © (14.10.04 15:44)
> Действительно, не обрабатывался WAIT_TIMEOUT;
Я про это не говорил - пропустил.
Ошибка то осталась.
WAIT_OBJECT_0:
begin
for i :=0 to Length(FPool)-1 do
begin
if FPool[i].FThreadFree then
begin
FPool[i].FThreadFree := False;
FPool[i].FExecProc := PQueueRec(FListQueue[0]).rProc;
FPool[i].FParm := PQueueRec(FListQueue[0]).rParm;
Dispose(PQueueRec(FListQueue[0]));
FListQueue.Delete(0);
Result := True;
FPool[i].Resume;
Exit;
end;
end;
NewThread;
здесь не задается Result в случае занятости всех потоков, НЕдостижения максимума потоков и наличия заданий, а должен т.к. иначе при определенных условиях не будет правильно отрабатывать while StartThreadFromQueue do; end;
Про медленность объектов ядра:
извини уж, это полная как бы ерунда. Твой код в определенных случаях от потери производительности страдал (сейчас вроде исправил) много больше, чем он может пострадать от объектов ядра.
Все же, есть способы объходиться без ОЯ. Например см. класс TMultiReadExclusiveWriteSynchronizer. Там реализована очень скорострельная синхронизация БЕЗ объектов ядра. Если интересно смотри его.
panov (2004-10-14 15:48:00) [88]>Суслик © (14.10.04 15:42) [22]
А какая разница, сколько раз Resume вызывать?
По поводу [14] и [15] -
if FListQueue.Count>0 then //В очереди есть задания
begin
while StartThreadFromQueue do; //Передать в пул на выполнение
end;
Та же самая ситуация возникает, что и с TThreadPoool: поток не успевает начать выполнять метод TThreadExecuter.Execute
panov (2004-10-14 15:44:00) [89]>Суслик © (14.10.04 15:39) [21]
Я понимаю, что можно использовать объекты ядра для ожидания.
Но нет ли более быстрого решения?
Создание и использование таких объектов занимает время и ресурсы.
Мне кажется, что для единовременного использования это неоправданно.
>Суслик © (14.10.04 15:31) [17]
Спасибо.
Действительно, не обрабатывался WAIT_TIMEOUT;
Изменил, теперь выглядит так:
function TThreadPool.StartThreadFromQueue: Boolean;
var
RC: Integer;
i: Integer;
begin
Result := False;
if FListQueue.Count=0 then Exit; //? ??????? ??? ??????? - ?????
RC := WaitForSingleObject(FSemaphore,0);
case RC of
WAIT_OBJECT_0:
begin
for i :=0 to Length(FPool)-1 do
begin
if FPool[i].FThreadFree then
begin
FPool[i].FThreadFree := False;
FPool[i].FExecProc := PQueueRec(FListQueue[0]).rProc;
FPool[i].FParm := PQueueRec(FListQueue[0]).rParm;
Dispose(PQueueRec(FListQueue[0]));
FListQueue.Delete(0);
Result := True;
FPool[i].Resume;
Exit;
end;
end;
NewThread;
end;
WAIT_TIMEOUT:
begin
NewThread;
end;
end;
end;
procedure TThreadPool.NewThread;
var
i: Integer;
begin
if Length(FPool)<FMaxThreads then
begin
i := Length(FPool);
SetLength(FPool,i+1);
FPool[i] := TThreadExecuter.Create(Self.ThreadID,i);
end;
end;
Суслик (2004-10-14 15:42:00) [90]
> [19] panov © (14.10.04 15:35)
Плохо.
А если поток уже делом занят?
Будет двойной вызов resume.
Лучше пользоваться event'ом.
Суслик (2004-10-14 15:39:00) [91]
> [20] Суслик © (14.10.04 15:37)
пояснения
var
E: TEvent;
// Главный поток
pooler := TPool.Create();
E.WaitFor;
// Поток-элемент пулера
procedure TThreadExecutor.Execute;
begin
Создание очереди
E.SetEvent;
...
end;
Все.
Суслик (2004-10-14 15:37:00) [92]
> [18] panov © (14.10.04 15:33)
Посмотри на ответ [6].
Там приведено общее решение: в главном потоке ждать TEvent, который будет установлен после создания очереди.
panov (2004-10-14 15:35:00) [93]>Суслик © (14.10.04 15:28) [16]
С терминированием - спасибо, упустил из вида.
for i := 0 to Length(FPool)-1 do
begin
FPool[i].Terminate;
FPool[i].Resume;
end;
panov (2004-10-14 15:33:00) [94]Метод с inherited(False) не приводит к нужному результату.
Строка
PostThreadMessage(Self.ThreadID,WM_AddThread,Integer(@aProc),Integer(aParm));
Выполняется раньше, чем код в Execute
PeekMessage(Msg,0,0,0,pm_NoRemove);
В результате выполнения
M := TThreadPool.Create(5);
for i := 0 to 100 do
begin
New(Parm);
Parm.n := i;
Parm.TimeOut := (Random(10)+1)*30;
M.AddThread(Exec1,Parm);
end;
т.е. обращение к методу AddThread начинается раньше, чем стартует метод Execute.
Суслик (2004-10-14 15:31:00) [95]Еще
в function TThreadPool.StartThreadFromQueue: Boolean;
в случае занятости делом всех потоков и недостижения макс. кол-ва потоков не верен будет результат: result будет равен false! Т.е. не все потоки будут запущены.
Суслик (2004-10-14 15:28:00) [96]Еще
destructor TThreadPool.Destroy;
var
i: Integer;
begin
...
//Очистка пула потоков
for i := 0 to Length(FPool)-1 do FPool[i].Terminate; ...
end;
О какой очистке пула может идти речь, если потоки в нерабочем состоянии находятся в режиме suspended.
Я бы лучше не делал потокам resume\suspend, а сажал их на ожидаение tevent
panov (2004-10-14 15:27:00) [97]>Суслик © (14.10.04 15:14) [14]
Спасибо за совет, добавлю
while StartThreadFromQueue do;
вместо if FListQueue>0 while...
Суслик (2004-10-14 15:14:00) [98]Я бы сюда
if Msg.message=WM_EndThreadExecuter then
begin
FPool[Msg.wParam].FThreadFree := True; //Закончилось выполнение
//задания в пуле
ReleaseSemaphore(FSemaphore,1,nil); //Увеличить счетчик свободных
//потоков в пуле
Continue;
end;
перед (или вместо) continue добавил бы вот этот кусочек
if FListQueue.Count>0 then //В очереди есть задания
begin
while StartThreadFromQueue do; //Передать в пул на выполнение
end;
Это позволит не ждать 10 мс в случае если в очереди есть необработанные запросы.
Суслик (2004-10-14 15:09:00) [99]
> [12] panov © (14.10.04 15:07)
из всего, что я сказал, скорее всего был не прав в этой фразе "Именно так и надо делать в дельфи 6." Тут надо еще подумать.
Но, то, что resume в коснтурторе делать нехорошо в Д6 это точно. Спасибо sha. Навел меня как-то на эту мысль.
---------------
Как решение обозначенной в вопросе проблемы с событием? Оно точно поможет, я всегда так делаю.
panov (2004-10-14 15:07:00) [100]>Суслик © (14.10.04 15:02) [11]
С AfterConstruction сейчас проверю