Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Основная";
Текущий архив: 2003.10.06;
Скачать: [xml.tar.bz2];

Вниз

Как лучше управлять потоками в этом случае?   Найти похожие ветки 

 
w666w   (2003-09-19 16:14) [0]

Уважаемые мастера! Подскажите наилучший способ решения следующей проблемы:

Один поток (TMasterThread) является администратором дочерних потоков (TSlavaThread). В нем есть массив, каждым элементом которого является, соответственно, дочерний поток. Проблема в том, что я никак не могу понять, каким образом сообщить потоку-мастеру, что дочерний поток завершился? Код:

Const
MSG_MASTER_THREAD_STARTED = WM_USER + 1;
MSG_MASTER_THREAD_WORKING = WM_USER + 2;
MSG_MASTER_THREAD_TERMINATED = WM_USER + 3;
MSG_SLAVE_THREAD_STARTED = WM_USER + 4;
MSG_SLAVE_THREAD_WORKING = WM_USER + 5;
MSG_SLAVE_THREAD_TERMINATED = WM_USER + 6;

type

TSlaveThread = class;

TSlaveThread = class(TThread)
protected
fName : string;
procedure Execute; override;
private
public
constructor Create(aSuspended : boolean; aName : string);
destructor Destroy; override;
property Name : string read fName write fName;
end;

TMasterThread = class(TThread)
protected
fThreadsArray : array of TSlaveThread;
procedure Execute; override;
private
public
constructor Create(aSuspended : boolean);
destructor Destroy; override;
end;

TForm1 = class(TForm)
ListView1: TListView; // ListView


 
pasha_golub   (2003-09-19 16:18) [1]

procedure TSlaveThread.Terminate(Sender:TObject);
begin
PostThreadMessage тра-ля-ля
end;


 
w666w   (2003-09-19 16:21) [2]

Можно пример кода в MasterThread, который будет ловить это сообщение?


 
Владислав   (2003-09-19 16:26) [3]

Во-первых, это слишком громкое утверждение:

constructor TSlaveThread.Create(aSuspended : boolean; aName : string);
begin
FreeOnTerminate := true;
fName := aName;
inherited Create(aSuspended);
SendMessage(Form1.Handle, MSG_SLAVE_THREAD_STARTED, Integer(@Self), 0);
end;


Таки, ИМХО, это нужно в Execute. Аналогичное - то же самое.

В остальном... ИМХО, дай возможность Мастеру управлять объектом Slave. Пусть он сам создает и уничтожает такие объекты. Соответственно FreeOnTerminate := true; не нужен. Последней строкой Execute сообщение, о том, что функция потока завершается. Мастер после этого делает нужные действия. "Чистит" массив, прочее.

Удачи.


 
w666w   (2003-09-19 16:35) [4]

>> Владислав
>> Таки, ИМХО, это нужно в Execute. Аналогичное - то же самое.
MSG_SLAVE_THREAD_STARTED в принципе можно и в начале Execute, не спорю. Но про аналогичное (я так понял это про MSG_SLAVE_THREAD_TERMINATED) однозначно не согласен ставить его к конец Execute. Представь ситуацию, что в Execute TSlavaThread произошло исключение. Что получится? Поток автоматом идет на деструктор, пропуская весь код до конца Execute. Соотв. из LIstView он уже никогда не удалиться.

Насчет EverySlave.OnTreminate = TMasterThread.OnSlaveTerminate - интересно, но боюсь конфликтов несинхронизированного доступа. Вот если бы потоки порождал основной поток, то без опаски можно
EverySlave.OnTreminate = Form1.OnSlaveTerminate. А так мне кажется опасно. Или я ошибаюсь?


 
Sandman25   (2003-09-19 16:39) [5]

[4] w666w (19.09.03 16:35)

Slave.Create выполняется в контексте главного потока - получаем, что поток сам себе сообщение шлет, когда новый поток еще не запустился.


 
w666w   (2003-09-19 16:47) [6]

>> Sandman25
1. Неужели конструктор любого дополнительного потока выполняется в контексте главного потока? Это точно?
2. Разве если конструктор выполнился до послендей строчки, это не значит, что он однозначно перейдет на Execute?
3. Приведенный код работает в связи с чем и вызваны сомнения...


 
w666w   (2003-09-19 16:50) [7]

Неужели в этом случае

procedure TMasterThread.Execute;
....
begin
....
fThreadsArray[i] := TSlaveThread.Create(false, "Thread" + IntToStr(Random(10000)));
....
end;

SlaveThread будет создаваться в контексте главного потока?


 
Sandman25   (2003-09-19 16:55) [8]

1. Конструктор нового потока вызывается в контексте создающего потока. Ну а где же ему еще вызываться, если новый поток пока даже не создан.
2. Execute будет выполняться в другом потоке.
3. Естественно, работает. А в чем там проблема?

[7] w666w (19.09.03 16:50)
procedure TMasterThread.Execute;
....
begin
....
fThreadsArray[i] := TSlaveThread.Create(false, "Thread" + IntToStr(Random(10000)));
....
end;

>SlaveThread будет создаваться в контексте главного потока?

Нет, не главного, а того, кто его создает, то есть TMasterThread.


 
w666w   (2003-09-19 17:09) [9]

[8] Sandman25
1. SendMessage расположен после inherited Create - разве поток после этого новый еще не запустился?
2. Естественно, именно в том, который появляется после inherited
3. Проблема не в запуске или остановке - проблема в уведомлении Master"а об умирании Slava.

Как один из выходов из ситуации, можно передавать в Slave.Create ссылку на массив fThreadsArray (мастера) и его индекс в этом массиве. А в деструкторе Slave сам позаботится, чтобы выставить fThreadsArray[его_индекс] := nil. Но это не красиво, и на первый взгляд небезопасно. Есть ли красивые решения, проблема то популярная я думаю...


 
Master   (2003-09-19 17:17) [10]

Перепиши ты это дело и явно вызывай методы родителя. И правильно тебе говорять что методы оповещения поставь в Execute. Поскольку если поток создан, то необезательно пойдет на execute. Например замороженый тред.
В Create подчиненого треда передавай сылку на родителя. Опиши методы родителя типа ThreadWorking в них будеш SetEvent делать. А у родителя в execute WaitForMultipleObjects и делай что тебе надо.


 
Sandman25   (2003-09-19 17:23) [11]

[9] w666w (19.09.03 17:09)

1. По-Вашему, сразу после inherited Create родительский поток возвращается по адресу из стека, а новый поток продолжает выполнение с того места, где остановился родитель?


 
w666w   (2003-09-19 17:26) [12]

[10] Master
Это уже дело, хоть направление для движения начал видеть.
В принципе о методах оповещения изначально вопросов у меня не было, но почему то именно о них ижет основная дискуссия. Хорошо, давайте обсудим. Опять таки согласен с тем, что оповещение о старте потока можно перенести в начало Execute, даже, возможно нужно. НО НИКАК НЕ ОПОВЕЩЕНИЕ ОБ ОКОНЧАНИИ (см. мое мнение в [4]).

>> Поскольку если поток создан, то необезательно пойдет на execute.
Не знал честно говоря, что такое возможно. Т.е. насколько я понимаю он пойдет либо на Execute, либо на деструктор (если чтото нелепое произошло), так? Куда же еще ему податься?

>> Перепиши ты это дело и явно вызывай методы родителя
Можно описать поподробнее, о каких методах родителя идет речь

>> Опиши методы родителя типа ThreadWorking в них будеш SetEvent делать. А у родителя в execute WaitForMultipleObjects и делай что тебе надо.

К сожалению не имею никакого опыта работы с SetEvent и WaitForMultipleObjects. Я для того полный код и выложил, чтобы увидеть измененные куски здесь. Если не трудно, покажите как.


 
w666w   (2003-09-19 17:34) [13]

[11] Sandman25
Мне просто интересно понять, в какой момент времени происходит рождение нового потока "физически" - после inherited Create или при входе в Execute.

Я понял, что уведомление о старте (SendMessage(Form1.Handle, MSG_SLAVE_THREAD_STARTED, Integer(@Self), 0);
) отправляется в контексте MasterThread, а не в контексте SlaveThread. Но разве это важно, если поток уже создался (inherited пройден)?

И кстати, при FreeOnTerminate = true деструктор отработает в контексте какого потока? Родителя (MasterThread) или в собственном?


 
Владислав   (2003-09-19 17:35) [14]

"1. Неужели конструктор любого дополнительного потока выполняется в контексте главного потока? Это точно?"

Не точно. В контексте того потока, который его создает (вызывает конструктор).

"2. Разве если конструктор выполнился до послендей строчки, это не значит, что он однозначно перейдет на Execute?"

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

"3. Приведенный код работает в связи с чем и вызваны сомнения..."

Работает любой код, который компилятор "пропустил". Только некоторый неправильно.

"Насчет EverySlave.OnTreminate = TMasterThread.OnSlaveTerminate - интересно, но боюсь конфликтов несинхронизированного доступа. Вот если бы потоки порождал основной поток, то без опаски можно
EverySlave.OnTreminate = Form1.OnSlaveTerminate. А так мне кажется опасно. Или я ошибаюсь?"


Я тебе рекомендации давал такие, которые позволяют избежать конфликтов доступа. Поскольку ты можешь "обнулить" ссылки на объект в контексте того же управляющего потока. А он не может одновременно обращаться к ссылке, и обнулять ее. О вот все остальное приводит именно к этому.

"1. SendMessage расположен после inherited Create - разве поток после этого новый еще не запустился?"

Не факт.

"Как один из выходов из ситуации, можно передавать в Slave.Create ссылку на массив fThreadsArray (мастера) и его индекс в этом массиве. А в деструкторе Slave сам позаботится, чтобы выставить fThreadsArray[его_индекс] := nil. Но это не красиво, и на первый взгляд небезопасно. Есть ли красивые решения, проблема то популярная я думаю..."

Ну да. А чуть позже задашь вопрос типа "у меня AV по такому то адресу", потому что ты с одной стороны пытаешься "контролировать" потоки из Мастера, с другой стороны, из Slave"ов "лезешь" в Мастер. Сам посуди, бардак получается. Отсюда и проблемы прочие. Порядок должен быть :-)


 
Владислав   (2003-09-19 17:40) [15]

"Мне просто интересно понять, в какой момент времени происходит рождение нового потока "физически" - после inherited Create или при входе в Execute."

После Create и перед Execute. Между после и перед тоже есть промежутки.

"Я понял, что уведомление о старте (SendMessage(Form1.Handle, MSG_SLAVE_THREAD_STARTED, Integer(@Self), 0);
) отправляется в контексте MasterThread, а не в контексте SlaveThread. Но разве это важно, если поток уже создался (inherited пройден)?"

Пойми, создание объекта класса TThread и начало работы потока (если можно так выразиться, момент, когда ОС "выдает" квант времени на выполнение нового потока) не есть тоже самое.


 
w666w   (2003-09-19 17:48) [16]

[15] Владислав

Понял, спасибо, но насчет

SendMessage(Form1.Handle, MSG_SLAVE_THREAD_TERMINATED, Integer(@Self), 0);

именно в деструкторе я прав или нет?


 
panov   (2003-09-19 17:56) [17]

Самое простое решение:

В slave-поток передаешь указатель на master-поток.
При старте slave-потока вызываешь процедуру

TMasterThread.StartSlave(aSlaveThread: TSlaveThread);

при завершении - TMasterThread.EndSlave(aSlaveThread: TSlaveThread);

unit Unit2;

interface

uses
Classes,windows;

type
TSlaveThread=class(TThread);
TMasterThread = class(TThread)
private
FCS: RTL_CRITICAL_SECTION;
FListSlaveThread: TList;
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy;
procedure StartSlave(aSlaveThread: TSlaveThread);
procedure EndSlave(aSlaveThread: TSlaveThread);
end;

implementation

{ TMasterThread }

constructor TMasterThread.Create;
begin
inherited Create(True);
InitializeCriticalSection(FCS);
FListSlaveThread := TList.Create;
Resume;
end;

destructor TMasterThread.Destroy;
var
i: Integer;
begin
for i := FListSlaveThread.Count-1 downto 0 do
begin
TSlaveThread(FListSlaveThread[i]).Terminate;
end;
i := 10000;
while FListSlaveThread.Count>0 do
begin
Sleep(1);
Dec(i);
if i<0 then break;
end;
FListSlaveThread.Free;
DeleteCriticalSection(FCS);
inherited;
end;

procedure TMasterThread.StartSlave(aSlaveThread: TSlaveThread);
begin
EnterCriticalSection(FCS);
FListSlaveThread.Add(aSlaveThread);
LeaveCriticalSection(FCS);
end;

procedure TMasterThread.EndSlave(aSlaveThread: TSlaveThread);
var
i: Integer;
begin
EnterCriticalSection(FCS);
for i := 0 to FListSlaveThread.Count-1 do
begin
if TSlaveThread(FListSlaveThread[i])=aSlaveThread then
begin
FListSlaveThread.Delete(i);
break;
end;
end;
LeaveCriticalSection(FCS);
end;

procedure TMasterThread.Execute;
begin
while not Terminated do Sleep(1);
end;

end.


 
Sandman25   (2003-09-19 17:56) [18]

procedure TSlave.Execute(..)
begin
сообщение о создании Thread
try
while not Terminated do
...
finally
сообщение об удалении Thread
end;
end;


 
panov   (2003-09-19 18:01) [19]

>w666w (19.09.03 17:34) [13]
Мне просто интересно понять, в какой момент времени происходит рождение нового потока "физически" - после inherited Create или при входе в Execute.

Если в конструкторе потока использовать inherited Create(False), то немедленно стартует на выполнение процедура потока ( Execute).


 
w666w   (2003-09-19 18:08) [20]

[17] panov

Большое спасибо за помощь. А для чего используются CS, если MasterThread один?

[19] panov - [15] Владислав

Разноречивые посты в том смысле, что я говорил, что в коде

inherited Create(False);
SendMessage(params);

SendMessage произойдет только после того как новый поток уже запустился (panov), однако по мнению Владислава это не факт. Поясните плиз.


 
w666w   (2003-09-19 18:19) [21]

[17] panov

>>При старте slave-потока вызываешь процедуру
>>TMasterThread.StartSlave(aSlaveThread: TSlaveThread);
в Execute или в Create Slave потока?

>>при завершении - TMasterThread.EndSlave(aSlaveThread: TSlaveThread);

опять таки в конце Execute или в деструкторе?

В деструкторе Master"а нельзя ли заменить

i := 10000;
while FListSlaveThread.Count>0 do
begin
Sleep(1);
Dec(i);
if i<0 then break;
end;

на

while FListSlaveThread.Count > 0 do
Sleep(1);
end;

Или все же есть опасность, что какой-либо из потоков не сообщит о своем окончании и FListSlaveThread.Count будет всегда больше нуля?


 
panov   (2003-09-19 18:25) [22]

SendMessage произойдет только после того как новый поток уже запустился (panov), однако по мнению Владислава это не факт. Поясните плиз.

Смотрим исходник:

constructor TThread.Create(CreateSuspended: Boolean);
{$IFDEF LINUX}
var
ErrCode: Integer;
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;

После выполнения строки
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);

в случае вызова BeginThread с флагом CREATE_SUSPENDED=False -

function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord;
ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord;
var ThreadId: LongWord): Integer;
var
P: PThreadRec;
begin
New(P);
P.Func := ThreadFunc;
P.Parameter := Parameter;
IsMultiThread := TRUE;
Result := CreateThread(SecurityAttributes, StackSize, @ThreadWrapper, P,
CreationFlags, ThreadID);
end;

Процедура потока немедленно начинает выполняться.


 
Serginio666   (2003-09-19 18:26) [23]

Кроме PostThreadMessage еще существуют CoMarshalInterface и CoUnMarshalInterface и их производные CoMarshalInterThreadInterfaceInStream и CoGetInterfaceAndReleaseStream.
Так же APC QueueUserAPC.


 
Sandman25   (2003-09-19 18:26) [24]

>SendMessage произойдет только после того как новый поток уже запустился (panov), однако по мнению Владислава это не факт. Поясните плиз.

Новый поток поставлен в "очередь потоков". Когда дойдет до него очередь, процессор начнет его выполнять. Но когда это произойдет, никто не знает. Поэтому может быть, сначала завершится Create в родительском потоке, а может быть и нет, если родителский поток будет приостановлен и начнется выполнение дочернего потока. Зависит от того, сколько времени осталось в кванте (времени) родительского потока.


 
w666w   (2003-09-19 18:30) [25]

Рассуждения Sandman25 кажутся вполне логичными, как Вам кажется, г-н panov?


 
w666w   (2003-09-19 18:52) [26]

Итак, с учетом мнений, высказанных [Владислав, Sandman25, panov] переписал код. Я думаю всем учавствовавшим в дискуссии будет интересно посмотреть на результат. Вот что получилось:

Const
MSG_MASTER_THREAD_STARTED = WM_USER + 1;
MSG_MASTER_THREAD_WORKING = WM_USER + 2;
MSG_MASTER_THREAD_TERMINATED = WM_USER + 3;
MSG_SLAVE_THREAD_STARTED = WM_USER + 4;
MSG_SLAVE_THREAD_WORKING = WM_USER + 5;
MSG_SLAVE_THREAD_TERMINATED = WM_USER + 6;

type

TSlaveThread = class;

TMasterThread = class(TThread)
private
fCS: RTL_CRITICAL_SECTION;
fSlaveThreads: TList;
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy;
procedure OnSlaveDone(Sender: TObject);
end;

TSlaveThread = class(TThread)
protected
fName : string;
procedure Execute; override;
private
public
constructor Create(aSuspended : boolean; aMasterThread : TMasterThread; aName : string);
property Name : string read fName write fName;
end;

TForm1 = class(TForm)
ListView1: TListView; // ListView


 
panov   (2003-09-19 18:53) [27]

Рассуждения Sandman25 кажутся вполне логичными, как Вам кажется, г-н panov?

Вполне логично, и стыкуется подтверждает panov © (19.09.03 18:25) [22].

Если ты хочешь полностью владеть ситуацией, то создавать поток нужно с флагом CREATE_SUSPENDED = TRUE.

Только в этом случае все строки конструктора ГАРАНТИРОВАННО будут исполнены ДО начала процедуры потока.

Последней командой в конструкторе потока должна быть команда Resume.


 
panov   (2003-09-19 19:01) [28]

Когда я приводил пример, я не это имел ввиду.

Совершенно нет необходимости организовывать обработку очереди сообщений.

при таком методе, который реализован в приведенном тобой коде нет необходимости в критической секции.

В моем примере процедуры StartSlave и EndSlave вызываются в slave-потоке, и в любое время несколько потоков одновременно могут обратиться к ним, поэтому и необходима критическая секция.


 
w666w   (2003-09-19 19:10) [29]

а OnSlaveDone? Там необходима CS?


 
panov   (2003-09-19 19:16) [30]

Если ты обращение к переменным выполняешь только из одного потока, то необходимости в критических секциях нет.


 
w666w   (2003-09-19 19:21) [31]

Но насколько я понимаю OnSlaveDone будет вызвана в контексте потока MasterThread, и вызвать эту процедуру могут одновременно несколько SlaveThread. Получается, что там CS все же нужна?


 
panov   (2003-09-19 19:46) [32]

Постановка задачи у тебя отличается от реализации.
Мастер-поток не занимается у тебя диспетчеризацией дочерних потоков в приведенном коде.


 
panov   (2003-09-19 19:49) [33]

>w666w (19.09.03 19:21) [31]
Но насколько я понимаю OnSlaveDone будет вызвана в контексте потока MasterThread, и вызвать эту процедуру могут одновременно несколько SlaveThread. Получается, что там CS все же нужна?

Читаем Help:

Write an OnTerminate event handler to execute code after the thread finishes executing. The OnTerminate event handler is called in the context of the main VCL thread, which means VCL methods and properties can be called freely.

Поэтому и в этом случае крит. секции не нужны, так как процедура всегда выпоняется в контексте master-потока.


 
w666w   (2003-09-25 16:57) [34]

Добавил в TSlaveThread деструктор (Destroy; override;)
Непойму, почему в TMasterThread.OnSlaveDone при fSlaveThreads.Delete(i); не вызывается деструктор TSlaveThread?

Делаю вместо этого TSlaveThread(fSlaveThreads[i]).Free; - деструктор вызывается, но не выполняет inherited. Почему? Каким образом в этом случае поступать?



Страницы: 1 вся ветка

Форум: "Основная";
Текущий архив: 2003.10.06;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.56 MB
Время: 0.008 c
3-18266
rava
2003-09-15 16:49
2003.10.06
Исключение MAX()


1-18381
lak
2003-09-22 15:48
2003.10.06
вычисление выражений в префиксной записи


1-18357
Olivka
2003-09-23 09:40
2003.10.06
Чем заменить DirectoryExists (платформо-независимую бы)?


1-18388
Barlok
2003-09-25 14:42
2003.10.06
Вопрос по OpenGL книга Краснов?


7-18651
Kremen
2003-07-22 15:23
2003.10.06
Подскажите пожалуйста какой-нибудь компонент для работы с СОМ-пор





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский