![]() |
| ||
|
|
Новости |
Поиск |
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 15:02:00) [101]
> [10] panov © (14.10.04 14:59)
> После выполнения выделенной строки выполнение поточной функции
> начнется немедленно.
ну бог в помощь :)
> CREATE_SUSPENDED
а этааа что?
panov (2004-10-14 14:59:00) [102]>Суслик © (14.10.04 14:54) [8]
Привожу код TThread.Create:
constructor TThread.Create(CreateSuspended: Boolean);
begin
inherited Create;
AddThread;
FSuspended := CreateSuspended;
FCreateSuspended := CreateSuspended;
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
if FHandle = 0 then
raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);
end;
После выполнения выделенной строки выполнение поточной функции начнется немедленно.
Суслик (2004-10-14 14:56:00) [103]
> К сожалению, для реализации стандартных потоков(TThread)
> в Delphi нельзя сделать Rerasing
Что значит нельзя?
Используешь AcquireExceptionObject и делаешь reraise как угодно.
См. исходный код classes для класса tthread.
panov (2004-10-14 14:54:00) [104]>Суслик © (14.10.04 14:49) [4]
Замечание-вопрос 2
Почему в TThreadExecuter.Execute "глушится" exception? Не лучше ли бы было его как-то обработать?
Замечание 3.
Скорее всего, обработка будет Exception будет добавлена.
К сожалению, для реализации стандартных потоков(TThread) в Delphi нельзя сделать Rerasing(хотя у меня есть сторонняя реализация от y-soft, которую я обязательно разберу).
Замечание 4.
Хорошее замечание, исправлю.
Суслик (2004-10-14 14:54:00) [105]
> [5] panov © (14.10.04 14:49)
> Так делать нельзя.
Ты не прав. Именно так и надо делать в дельфи 6. Не веришь? См. исходный код TThread.Create, AfterContruction и FCreateSuspended
Суслик (2004-10-14 14:53:00) [106]
> Вопрос:
> После выполнения конструктора из TThreadPool проходит некоторое
> время, прежде чем будет создана очередь сообщений для потока.
> После M := TThreadPool.Create(5) поток использовать нельзя
> до создания очереди сообщений.
Я бы сделал так.
1) Инстанцировал TThreadPool например, через метод класса TThreadPool. Например так
class function TThreadPool.Counsturct: TTHreadPool;
begin
Result := TThreadPool.Create(...);
SomeEvent.WaitFor;
end;
2) Событие SomeEvent устанавливал бы в signaled в TThreadPool.Execute после создания очереди потока.
Суслик (2004-10-14 14:49:00) [107]Замечание-вопрос 2
Почему в TThreadExecuter.Execute "глушится" exception? Не лучше ли бы было его как-то обработать?
Замечание 3.
Аналог первого замечания для TThreadPool.Create. Хотя, здесь эта маловероятна ошибка.
Замечание 4.
В destructor TThreadPool.Destroy почему пишешь
for i := 0 to FListQueue.Count-1 do Dispose(PQueueRec(FListQueue[0]));
а нет
if Assigned(FListQueue) then for i := 0 to FListQueue.Count-1 do Dispose(PQueueRec(FListQueue[0]));
?
пока все - дальше времени нет смотреть :)
panov (2004-10-14 14:49:00) [108]>Суслик © (14.10.04 14:43) [2]
constructor TThreadExecuter.Create(const aOwnerThread: THandle;const aNumThread: Integer);
begin
inherited Create(False);
FreeOnTerminate := True;
FThreadFree := True;
FThreadIdOwner := aOwnerThread;
FNumThread := aNumThread;
end
Так делать нельзя.
Послеinherited Create(False);немедленно стартует методExecute, соответственно, остаются непроинициализированными переменные
FreeOnTerminate := True;
FThreadFree := True;
FThreadIdOwner := aOwnerThread;
FNumThread := aNumThread;
и метод Execute будет обращаться к еще неинициализированным переменным.
panov (2004-10-14 14:46:00) [109]Вопрос:
После выполнения конструктора из TThreadPool проходит некоторое время, прежде чем будет создана очередь сообщений для потока.
ПослеM := TThreadPool.Create(5)поток использовать нельзя до создания очереди сообщений.
Как лучше обработать эту ситуацию?
Сейчас временно в конструкторе присваивается высокий приоритет tpTimeCritical, а в методе Execute возвращается в исходное состояние.
Суслик (2004-10-14 14:43:00) [110]Замечание 1.
> constructor TThreadExecuter.Create(const aOwnerThread: THandle;const
> aNumThread: Integer);
> begin
> inherited Create(True);
> FreeOnTerminate := True;
> FThreadFree := True;
> FThreadIdOwner := aOwnerThread;
> FNumThread := aNumThread;
> Resume;
> end;
В зависимости от версии дельфи делать Resume в конструкторе не очень хорошо. Если поток отработает быстро, то будет ошибка в AfterContructrion, который в дельфи 6 выглядит так
procedure TThread.AfterConstruction;
begin
if not FCreateSuspended then
Resume;
end;
Я бы написал просто так
constructor TThreadExecuter.Create(const aOwnerThread: THandle;const aNumThread: Integer);
begin
inherited Create(False);
FreeOnTerminate := True;
FThreadFree := True;
FThreadIdOwner := aOwnerThread;
FNumThread := aNumThread;
end;
panov (2004-10-14 14:35:00) [111]
{ TThreadPool }
constructor TThreadPool.Create(aMaxThreads: Integer);
begin
inherited Create(True);
//Непонятно пока, как сделать с наименьшими потерями возможность
//полностью инициализироваться потоку(успеть создать очередь
//сообщений), так как высокий приоритет не гарантирует этого
Priority := tpTimeCritical;
FMaxThreads := aMaxThreads; //Максимальное число потоков в пуле
FreeOnTerminate := True;
FListQueue := TList.Create; //Очередь заданий на выполнение
FSemaphore := CreateSemaphore(nil,FMaxThreads,FMaxThreads,nil);
Resume;
end;
destructor TThreadPool.Destroy;
var
i: Integer;
begin
{ TODO -cВажно : Добавить обработку ошибок }
//Чистим очередь заданий
for i := 0 to FListQueue.Count-1 do Dispose(PQueueRec(FListQueue[0]));
FListQueue.Free;
//Очистка пула потоков
for i := 0 to Length(FPool)-1 do FPool[i].Terminate;
CloseHandle(FSemaphore);
inherited;
end;
procedure TThreadPool.Execute;
var
Msg: TMsg; //Приемник сообщений
p: Pointer; //Фиктивный указатель
RC: DWORD; //Код возврата
begin
{ TODO -cВажно : Защитить код try..finally..end }
PeekMessage(Msg,0,0,0,pm_NoRemove); //Создание очереди сообщений
//Возвращаем нормальный приоритет - очередь сообщений создана
Priority := tpNormal;
p := nil;
while not Terminated do
begin
//Ждем появления любого сообщения в очереди сообщений
rc := MsgWaitForMultipleObjects(0,p,False,10,QS_ALLPOSTMESSAGE);
{ TODO -cВажно : Обработать ошибку }
if rc=WAIT_FAILED then
begin
Terminate;
Continue;
end;
if rc= WAIT_TIMEOUT then
begin
if FListQueue.Count>0 then //В очереди есть задания
begin
while StartThreadFromQueue do; //Передать в пул на выполнение
end;
end;
if (rc=WAIT_OBJECT_0) or (PeekMessage(Msg,0,0,0,pm_NoRemove)) then
begin
GetMessage(Msg,0,0,0);
if Msg.message=WM_QUIT then
begin
Terminate;
Continue;
end;
if Msg.message=WM_EndThreadExecuter then
begin
FPool[Msg.wParam].FThreadFree := True; //Закончилось выполнение
//задания в пуле
ReleaseSemaphore(FSemaphore,1,nil); //Увеличить счетчик свободных
//потоков в пуле
Continue;
end;
if Msg.message=WM_AddThread then
begin
JobAdd(Pointer(Msg.wParam),Pointer(Msg.lParam)); //Добавить задание в очередь
while StartThreadFromQueue do; //Передать в пул на выполнение
Continue;
end;
end;
end;
end;
procedure TThreadPool.JobAdd(const aProc: TThreadProc;const aParm: Pointer);
var
JobRec: PQueueRec;
begin
New(JobRec);
JobRec^.rProc := aProc;
JobRec^.rParm := aParm;
FListQueue.Add(JobRec);
end;
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;
if Length(FPool)<FMaxThreads then //В пуле еще не максимальное
begin //количество потоков?
i := Length(FPool);
SetLength(FPool,i+1); //Добавляем поток в пул
FPool[i] := TThreadExecuter.Create(Self.ThreadID,i);
end;
end;
end;
end;
procedure TThreadPool.AddThread(aProc: TThreadProc; aParm: Pointer);
begin
//Пересылаем в очередь сообщений сообщение о новом задании
PostThreadMessage(Self.ThreadID,WM_AddThread,Integer(@aProc),Integer(aParm));
end;
initialization
//Регистрируем сообщение об окончании обработки задания
WM_EndThreadExecuter := RegisterWindowMessage('TThreadPoolWM_EndThreadExecuter');
//Регистрируем сообщение о появлении нового задания на обработку
WM_AddThread := RegisterWindowMessage('TThreadPoolWM_AddThread');
finalization
end.
Вызов происходит следующим образом(Создавал для тестирования):
type
PParmRec=^TParmRec;
TParmRec=record
n: Integer;
TimeOut: integer;
end;
procedure Exec1(aParm: pointer);
var
Parm: PParmRec;
begin
Parm := aParm;
Sleep(Parm.TimeOut);
Form1.lb.Items.Add('End '+IntToStr(Parm^.n)+'/'+IntToStr(Parm^.TimeOut));
Dispose(Parm);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
Parm: PParmRec;
i: Integer;
begin
Randomize;
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;
end;