| | | | |
Полный текст материала
Другие публикации автора: Сергей Гурин
Цитата или краткий комментарий: «... В статье будет рассмотрен только частный случай использования параллельных потоков - потоки, существующие в пределах одного приложения. ...» |
Важно:- Страница предназначена для обсуждения материала, его содержания, полезности, соответствия действительности и так далее. Смысл не в разборке, а в приближении к истине :о) и пользе для всех.
- Любые другие сообщения или вопросы, а так же личные эмоции в адрес авторов и полемика, не относящаяся к теме обсуждаемого материала, будут удаляться без предупреждения авторов, дабы не мешать жителям нормально общаться.
- При голосовании учитывайте уровень, на который расчитан материал. "Интересность и полезность" имеет смысл оценивать относительно того, кому именно предназначался материал.
- Размер одного сообщений не должен превышать 5К. Если Вам нужно сказать больше, сделайте это за два раза. Или, что в данной ситуации правильнее, напишите свою статью.
Всегда легче осудить сделанное, нежели сделать самому. Поэтому, пожалуйста, соблюдайте правила Королевства и уважайте друг друга.
Добавить свое мнение.
| | Содержит полезные и(или) интересные сведения | [1] | 10 | 76.9% | | | | Ничего особенно нового и интересного | [2] | 2 | 15.4% | | | | Написано неверно (обязательно укажите почему) | [3] | 1 | 7.7% | | Всего проголосовали: 13 | | | Все понятно, материал читается легко | [1] | 5 | 55.6% | | | | Есть неясности в изложении | [2] | 4 | 44.4% | | | | Непонятно написано, трудно читается | [3] | 0 | 0% | | Всего проголосовали: 9 |
[Потоки (нити) Threads] [Объекты синхронизации системы]
Отслеживать это обсуждение
Всего сообщений: 3515-09-2011 15:29Написал на основе наборов классов автора небольшое приложение, за что ему большое спасибо. Но уткнулся в одну проблему, никак понять не могу, где затык. Суть приложения в следующем. Менеджер создает минимум два потока, время жизни каждого 30 минут. Создает не сразу, а с некоторой задержкой, чтобы когда один поток заканчивает работу, другой, более "младщий" еще мог обрабатывать запросы, пока завершается "старший" и создается новый вместо него. Таким образом создается несколько потоков, которые обеспечивают непрерывность сервиса. Так вот, пока существует только один поток, после старта программы, пока не стартовал второй, первый прекрасно обслуживает запросы, как задумано. Но после того как создается второй поток, начинаются проблемы. Первый поток перестает обрабатывать запросы, такое ощущение, что он находится в остановленном состоянии. Процедура, которая определяет свободный, ожидающий данные поток и передает ему параметры, ниже:
procedure TDataThreadManager.PutRequestToChannel(SocketHandle: TSocket; Code: String);
var
hash: Integer;
th: TGsvThread;
AllBusy: boolean;
begin
AllBusy := True;
EnterCriticalSection(FLatch);
try
for hash := Low(FHashTable) to High(FHashTable) do begin
th := FHashTable[hash];
if Assigned(th) then begin
th.Suspend;
AllBusy := TMyThread(th).Busy;
if not AllBusy and not TMyThread(th).Terminated then begin
TMyThread(th).Code := Code;
TMyThread(th).InitialSocketHandle := SocketHandle;
end;
th.Resume;
if not AllBusy then Break;
end;
end;
finally
LeaveCriticalSection(FLatch);
end;
if AllBusy then Add(TMyThread.Create(SocketHandle, Code, 3), True);
end;
Метод Execute у потока:
procedure TMyThread.Execute;
begin
try
while not Terminated do begin
if FSessionID = '' then Initialize;
if Code <> '' then GetData;
Sleep(50);
end;
except
Terminated := True;
end;
end;
Когда отрабатывает GetData, она присваивает Code := '', и поток ожидает дальнейшего поступления данных. Вот я и не пойму, в чем проблема. Когда он один, то все отлично работает. Но как только создается второй, то все, первый "засыпает". Отлаживал эту ситуацию в PutRequestToChannel, после присваивания th := FHashTable[hash] смотрю свойства потока, когда несколько раз пытаюсь передать в него значение Code, а там так и сидит предыдущее значение, т.е. поток не работает, будто паузе висит. Что тут может быть, как думаете, товарищи? |
|
13-11-2007 12:00сообщение от автора материала Я думаю, что это неудачное решение - выполнять какие-либо значимые действия в завершающем событии. Это событие предназначено только для того, чтобы корректно завершить поток, а точнее, освободить те ресурсы, которые не связаны с памятью (например, открытые файлы). Мне кажется, что лучше разбор файла делать либо сразу после его загрузки, либо в отдельном потоке, либо поставить его в очередь и обработать в основном потоке |
|
13-11-2007 05:18Спасибо за ваш ответ!
Простите, но я похоже не понимаю как это сделать в моем случае :(
Есть простенький класс для получения файла из интернета.
constructor TGetInetFileThread.Create(const URL: string; Delay: Cardinal = 0);
begin
inherited Create;
FURL := URL;
FDelay := Delay;
FDownloaded := False;
HTTP := THTTPSend.Create;
end;
destructor TGetInetFileThread.Destroy;
begin
HTTP.Free;
end;
procedure TGetInetFileThread.Execute;
begin
if FDelay > 0 then Pause(FDelay);
HTTP.HTTPMethod('GET', FURL);
FDownloaded := True;
end;
И в классе-потомке переопределяется метод OnFinished:
TAppUpdateThread = class(TGetInetFileThread)
public
procedure OnFinished; override;
end;
При завершении приложения вызывается TerminateAll в OnCloseQuery
procedure TSpiderUpdateThread.OnFinished;
begin
if Downloaded then // Downloaded = True
begin
// parse downloaded file
end;
end;
Причем OnFinished срабатывает сразу же, но потом приложение еще некоторое время не завершается, как будто ждет завершения HTTP.HTTPMethod('GET', FURL); |
|
12-11-2007 21:33сообщение от автора материала Самостоятельное или внешнее завершение потока - это логически эквивалентные события. Если Вам требуется их различение, то в классе потока Вам нужно определить булеву переменную, которую поток будет явно устанавливать при самостоятельном завершении |
|
12-11-2007 16:43Подскажите, пожалуйтса, как узнать в OnFinished завершился ли поток сам или его уничтожили? (и Finished, и Terminated возвращают True) |
|
01-04-2007 01:14сообщение от автора материала Вы пишете: я не первый кто попросил от вас хотя бы один банальный пример применения вашей библиотеки многопоточности, который бы показал, всю ее красоту и способности.
Еще раз повторю свой ответ, сделанный несколькими сообщениями ранее: Эта работа является продолжением моей более ранней работы (библиотека Gala http://gurin.tomsknet.ru/gala.html). Библиотека Gala хорошо документирована, к ней прилагается много примеров и демонстрационных файлов.
Я не вижу смысла дублировать примеры и поэтому отсылаю к своей предыдущей работе. |
|
30-03-2007 14:56мдя....
Я вот что заметил, при использовании потоков TGsvThread выполняемая программа начинает дико жрать файл подкачки, пока не сожретего до того моента, когда система скажет - все, память кончилась. Ради эксперемента TGsvThread заменил на обычный TThread, и файл подкачки перестал расти и замедлять работу программы. Полчему это происходит?
Сергей, может я в чем то окажусь не прав, но у вас очень много слов, и я не первый кто попросил от вас хотябы один банальный пример применения вашей библиотеки многопоточности, который бы показал, всю ее красоту и способности. |
|
30-03-2007 10:05сообщение от автора материала К сожалению, Вы совсем неправильно понимаете сущность потоков. Потоки являются автономными параллельными (а точнее, квазипараллельными) сущностями, которые выполняются каждый сам по себе. То есть, всё что делает поток, он должен делать САМОСТОЯТЕЛЬНО, например, устанавливать значения собственных переменных, считывать данные из разделяемых контейнеров, взаимодействовать с другими параллельными потоками. Другие потоки могут только взаимодействовать с ним, вызывая его методы (иными словами - посылая ему сообщения). НИ В КОЕМ СЛУЧАЕ не следует из одного потока пытаться что-то изменить в другом потоке, ту же переменную spTxtFile. Вы можете передать ее, например, как аргумент конструктору Вашего потока. Частично поток может инициализировать свои данные в конструкторе (когда он выполняется в контексте родительского потока), частично перед началом своего основного цикла (уже в своем контексте) и все остальные изменения - в теле своего основного цикла. Получать дескриптор потока нужно только тогда, когда Вы выполняете взаимодействия между потоками. Например, если Вы хотите поместить передать потоку некоторые данные, Вы можете создать в нем защищенную очередь входных данных. Другие потоки ни к коем случае не должны самостоятельно работать с этой очередью - вместо этого в классе потока Вы должны предусмотреть метод, который сначала блокирует собственную очередь (с помощью критической секции), затем выполняет операции с очередью, и затем разблокирует очередь. У параллельного программирования есть свои законы, они существенно отличаются от последовательного программирования и, если Вы хотите создать работающую (неглючную) программу, Вы должны учитывать эти законы. Это относится не только к классу TGsvThread - это вообще свойственно любой многопоточной программе. |
|
30-03-2007 07:25С этим все понятно. Но опять получается проблема
Рабираем код:
type
TDBThread = class(TGsvThread)
private
...
public
spTxtFile : String;
...
protected
procedure Execute; override;
end;
...
pManager : TGsvThreadManager;
pManager := TGsvThreadManager.Create(64);
...
Handle := pManager.Add(TDBThread.Create, false);
Thread.Resume;
Если использовать конструкцию Thread := ThreadManager.Lock(Handle);
в моем случае это будет Thread := pManager.Lock(Handle);
но переменная Thread должна быть типа TGsvThread, и никак не TDBThread, соответственно я немогу достучаться до переменной spTxtFile : String;
Как быть в этом случае?
|
|
29-03-2007 09:52сообщение от автора материала Не станет никогда в принципе. Вопросы уничтожения потоков объясняются в статье очень подробно. Вот пример кода из статьи
Handle := ThreadManager.Add(TSomeThread.Create);
....
Thread := ThreadManager.Lock(Handle);
if Assigned(Thread) then
begin
....
end;
....
ThreadManager.Release(Handle);
Наличие (или отсутствие)переменной, ссылающейся на поток, никак не связано с временем жизни потока - им нужно управлять явно с помощью Lock и Release (а не наличием или отсутствием переменной). При создании потока и добавлении его к диспетчеру, счетчик ссылок становится равным 1. Для уничтожения потока нужно вызвать метод Release для дескриптора потока (но не уничтожать сам объект). Если у потока появляется еще один пользователь, то этот пользовать должен вызвать метод Lock, а после того, как поток стал ему не нужным - метод Release. Пользователей у потока может быть множество и поток не будет уничтожен до тех пор, пока все пользователи не вызовут метод Release данного потока. Еще раз подчерку - не желательно вообще где-то держать переменные, ссылающиеся на поток - для этого существуют дескрипторы. Причина, по которой используются дескрипторы (а не ссылки на объекты потоков) очень подробно разъясняется в статье
|
|
29-03-2007 01:43Еще несколько вопросов к Сергею Гурину:
Саздается поток
myPotok := myThread.Create;
потом передается в менеджер потоков
myMan.Add(myPotok);
1) В какой момент переменная myPotok станет (станет ли вообще) равная nil? |
|
27-03-2007 05:32Lord_Driver!
С вопросами, не касающимися статьи, добро пожаловать на Круглый стол! Если вы будете продолжать здесь, ваши сообщения будут удалены. |
|
27-03-2007 04:55Заработало!!! после установки другой версии сервера - Firebird-2.0.1.12855-1-Win32. Пока пробовал на 12 потоках, созданых "вручную", тоесть с жесткими параметрами что их 12 и каждому указан отдельный текстовый файл.
Но теперь проблема другая, некая особенность сервера Firebird (отметил это и на 1.0 и на 2.0), дело в том, что когда база достигает определенного размера, метров так 40, он резко теряет производительность, если вначале он кушал до 50% от ЦП, то к моменту размера базы в 40 метров, он кушает всего 10 - 15% от ЦП и соответственно падает его производительность.
Вопрос конешно не по теме, но может кто сталкивался, почему это происходит, и как Firebird сохранять производительность? |
|
27-03-2007 02:33Непонятно но факт.
Написал кусок кода, в котором потоки пока создаются руками (примерно так же как и в указанном мной уже тут коде).
В потоке реализовано чтение файла по строчно. Два потока очень даже резво читают данные из двух файлов паралельно. Казалось бы все хорошо, но тут возникает другая проблема, почемуто при соединении с базой второго потока, все вешается. Переменные для БД (Firebird-1.0.3.972-Win32) соединения созданы внутри процедуры обработки файла и перекрещиватся ну ни как не должны. Я пока в ступоре, незнаю что влияет на потоки, когда второй поток, пытается подцепится к базе. |
|
26-03-2007 14:11сообщение от автора материала > Вопрос, насколько опасно не терминировать потоки при закрытии формы в случае если они отработали?
Если поток отработал, то это автоматически означает его завершенность и то, что он будет уничтожен менеджером потоков (конечно, если на поток больше никто не ссылается). То есть, уничтожение потоков - это забота менеджера. Незавершенные потоки при закрытии формы нужно завершать с помощью метода TerminateAll менеджера. Процедура завершения состоит в том, что менеджер сначала уведомляет незавершенные потоки о необходимости конце работы (устанавливая свойство Terminate), но потоки должны самостоятельно отреагировать на свое завершение и выполнить корректную остановку.
> Каким способом можно организовать слежение за количеством одновременно запущенных потоков. и при освобождении потока (окончание обработки файла) запускать еще один.
Это классическая задача связанная с ограничением числа ресурсов. Наиболее адекватно она решается использованием счетного семафора. В этой статье семафоры не описаны, но их описание можно найти в любой книге по программированию в Windows или в моей статье о библиотеке Gala (gurin.tomsknet.ru/gala.html). Если не хотите разбираться с семафорами, то примитивно простое решение - периодически (например, по таймеру) получать от менеджера список активных потоков. Размер списка даст Вам число активных потоков. В общем же случае от таких решений стоит воздерживаться и использовать семафоры. Я специально сделал свойство Count менеджера скрытым, чтобы затруднить выполнение подобных некрасивых решений. |
|
26-03-2007 13:37Теперь обрисую кратко задачу, для которой нужна многопоточность:
Есть огромное количество текстовых файлов, которое нужно перелопачивать и запихивать в базу данных. Вот именно для этого и нужна многопоточность!!!
Ход решения простой, но доконца пока не дожат.
Нужно сканировать каталог, при наличии в нем файла, обработка файла запускается в потоке, но тут сразу появляется ограничение, одновременно обрабатываемых файлов, соответственно и потоков, должно быть строго фиксированное количество, скажем 255.
Файлов же в каталоге может быть до 3000 и более. То есть нужен один поток, который запускает обработку файлов в других потоках и следит чтобы одновременно небыло запущено более 255 потоков, т.к. если это делать не в потоке, форма будет неуправляемой и приложение тормазнутым.
А вот теперь вопрос к автору: Каким способом можно организовать слежение за количеством одновременно запущенных потоков. и при освобождении потока (окончание обработки файла) запускать еще один.
Решение вроде как бы на поверхности, завести какойнибудь глобальный массив, через который и следить за состоянием потоков, но мне кажется, что есть решение поизящнее.
Может как то использовать менеджер потоков, но его метод Add предполагает передачу в качестве параметра, уже готового потока, хотя что то наклевывается ... буду думать, но от предложений и коментариев не откажусь.
А вообще Автору огромное спасибо за предложенное решение по потокам. |
|
26-03-2007 13:36Сразу извинюсь, что немного поленился и не доконца разобрался.
И еще не растолковал саму задачу, ради которой нужна многопоточность.
Для начала, дабы раабелитироваться я приведу код, который я написал перед тем как прочитал последний пост автора.
И так, создаем свой клас унаследованный от TGsvThread в котором и переопределим метод Execute. Полчается следующее:
unit MyPotok;
interface
uses
GsvThread,
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TDBThread = class(TGsvThread)
private
public
iNum : Integer;
procedure WriteLog(sLogStr :String);
protected
procedure Execute; override;
end;
implementation
uses FMain;
procedure TDBThread.WriteLog(sLogStr :String);
Begin
sLogStr := IntToStr(iNum) + '->' + sLogStr;
SendMessage(FormMain.Handle, WM_THREAD_LOG, 0 , Integer(PChar(sLogStr)));
End;
procedure TDBThread.Execute;
var
i: Integer;
begin
WriteLog('= Начало работы потока');
for i:=1 to 1000 do
begin
WriteLog('Test ' + IntToStr(i));
end;
WriteLog('= Окончание работы потока');
end;
end.
Затем реализуем использование на форме:
unit FMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, GsvThread, MyPotok;
const
WM_THREAD_LOG = WM_USER + 1000;
type
TFormMain = class(TForm)
Memo1: TMemo;
Start: TButton;
procedure StartClick(Sender: TObject);
private
public
procedure OnThreadLog(var aMessage: TMessage);
message WM_THREAD_LOG;
end;
var
FormMain: TFormMain;
implementation
procedure TFormMain.OnThreadLog(var aMessage: TMessage);
Begin
Memo1.Lines.Add(pChar(aMessage.LParam));
End;
procedure TFormMain.StartClick(Sender: TObject);
var
pMan : TGsvThreadManager;
pPotok, pPotok1, pPotok2, pPotok3 : TDBThread;
begin
pMan := TGsvThreadManager.Create(1);
pPotok := TDBThread.Create;
pPotok.iNum :=1;
pMan.Add(pPotok);
pPotok1 := TDBThread.Create;
pPotok1.iNum :=2;
pMan.Add(pPotok1,true);
pPotok2 := TDBThread.Create;
pPotok2.iNum :=3;
pMan.Add(pPotok2,true);
pPotok3 := TDBThread.Create;
pPotok3.iNum :=4;
pMan.Add(pPotok3,true);
end;
end.
Все очень замечательно работает!!!
Кстати:
1) Как вы заметили в коде встречается закоментированная строка:
Если использовать PostMessage то в Memo1 строки появляются по порядку от каждого потока (1 1 1 ... 2 2 2 ... 3 3 3 ... ...), а при SendMessage в разноброд (1 2 3 ... 1 3 2 ... 3 2 1 ...)
2)Получилось передавать текст через LParam и вполне работает.
3) Вопрос, насколько опасно не терминировать потоки при закрытии формы в случае если они отработали?
|
|
26-03-2007 11:01сообщение от автора материала 1) Если посмотреть класс TGsvThread, то можно увидеть единственный абстрактный метод - Execute. Именно в этом методе производный от TGsvThread класс должен определить свой исполняемый код. В статье приведен пример переопределения этого метода:
procedure TDerivedThread.Execute;
begin
....
while not Terminated do begin
....
end;
end;
Поскольку класс TGsvThread содержит абстрактный метод, то по определению Вы не должны создавать его экземпляры. К сожалению, компилятор Delphi разрешает конструирование таких классов, но при вызове абстрактного метода возникает ошибка времени выполнения.
В своем примере Вы переопределили внутреннюю, скрытую функцию, основное назначение которой как раз и состоит в том, чтобы вызывать метод Execute у конкретного экземпляра класса. Функция ThreadProc
одна-единственная на все процессы, а конкретика каждого конкретного процесса описывается в Execute.
2) Напрямую поток ни в коем случае не должен взаимодействовать с VCL-компонентами. Это к Вашему вопросу "НЕСКОЛЬКО ПОТОКОВ ОДНОВРЕМЕННО ПИШУТ В Memo1 СТРОКИ ОТ 1 ДО 100. Уж очень сильна нужна многопоточность".
Для того, чтобы обойти принципиальную однопоточность VCL, используются различные средства. Стандартный компонент Delphi TThread предлагает для этого метод Synchronize. Класс TGsvThread использует для взаимодействия с VCL другой механизм события Windows - этому посвящен целый раздел в статье - он называется "Взаимодействие с VCL-потоком". Судя по Вашему вопросу Вам удобнее будет использовать именно стандартный TThread, так как реализация взаимодействия с VCL в нем реализуется проще. TGsvThread более удобен при динамическом создании и уничтожении множества потоков, взаимодействующих друг с другом, то есть, это несколько другая ниша.
|
|
26-03-2007 08:12Доброго времени суток, прочитал статью, посмотрел код, но так и не понял, где должен находится основной код, который должен выполняться в потоке.
На первый взгляд вроде тут:
function ThreadProc(p: TGsvThread): Integer;
var
i:integer;
begin
Result := 0;
with p do begin
for i:=1 to 100 do Form1.Memo1.Lines.Add(IntTostr(i) + ' - test');
ThreadExecute;
if Assigned(FManager) then
FManager.Release(FGsvHandle);
end;
EndThread(0);
end;
Вроде даже работает, если написать вот такой вызов:
procedure TForm1.Button1Click(Sender: TObject);
var
pMan : TGsvThreadManager;
pPotok, pPotok1, pPotok2, pPotok3 : TGsvThread;
begin
pMan := TGsvThreadManager.Create(1);
pPotok := TGsvThread.Create;
pPotok.iNum :=1;
pMan.Add(pPotok,true);
pPotok1 := TGsvThread.Create;
pPotok1.iNum :=2;
pMan.Add(pPotok1,true);
pPotok2 := TGsvThread.Create;
pPotok2.iNum :=3;
pMan.Add(pPotok2,true);
pPotok3 := TGsvThread.Create;
pPotok3.iNum :=4;
pMan.Add(pPotok3,true);
end;
Но я неуверен, что тут все корректно использую.
ХОТЕЛОСЬ БЫ ВСЕ ТАКИ ПОСМОТРЕТЬ НА КАКОЙ НИБУДЬ МАЛЕНЬКИЙ ПРИМЕР.
СКАЖЕМ НЕСКОЛЬКО ПОТОКОВ ОДНОВРЕМЕННО ПИШУТ В Memo1 СТРОКИ ОТ 1 ДО 100.
Уж очень сильна нужна многопоточность. |
|
21-01-2007 05:28Спасибо, буду разбираться. |
|
18-01-2007 05:29сообщение от автора материала Эта работа является продолжением моей более ранней работы (библиотека Gala http://gurin.tomsknet.ru/gala.html). Библиотека Gala хорошо документирована, к ней прилагается много примеров и демонстрационных файлов. Данную статью я дополнять не предполагал. |
|
18-01-2007 05:08Очень интересный материал, спасибо.
Уважаемый автор, а не могли бы вы дополнить статью простыми примерами? |
|
25-12-2006 04:42сообщение от автора материала К сожалению, я не знаком с набором компонентов Jv Threading. Гурин. |
|
25-12-2006 04:27Можно вопрос? В панели компонент от JEDI есть закладка Jv Threading
В ней 12 компонент, в том числе и какие-то manager'ы
Они реализуют эту функциональность, про которую вы написали? |
|
02-10-2005 10:11сообщение от автора материала Вы правы, пакет Gala в большей степени ориентирован на относительно небольшое количество интенсивно взаимодействующих потоков, причем время существования потоков относительно велико. Пакет GsvThread ориентирован на относительно большое число потоков с существенно различающимся временем жизни, причем взаимодействия потоков достаточно просты. В пакете Gala время жизни объектов, инкапсулирующих потоки, заканчивается только с завершением программы, в то время, как в пакете GsvThread время жизни потока определяет подсчетом ссылок, то есть поток живет до тех пор, пока он нужен хотя бы одному другому потоку. Кроме того, накладные расходы на поддержание механизма рандеву в пакете Gala относительно велики, но, с другой стороны, программировать взаимодействия очень удобно. В пакете GsvThread исключены эти накладные расходы, но вместо рандеву реализованы относительно простые каналы. |
|
01-10-2005 19:29У Вас на сайте помимо данной библиотеки нашел пакет Gala...
Как мне показалось, gsvThreads больше ориентирован на большое количество динамически создаваемых потоков, а Gala на их взаимодействие. Верно?
Точнее, предполагалось ли вобще какое-то разграничение их по сферам применения? :-) |
|
16-04-2005 11:16С Вашими замечаниями в принципе можно согласиться. Не оправдываясь, я попробую объяснить их причину.
"Весьма сомнительно использование трюка доступа к приватным свойствам и методам чужого класса в рамках одного юнита". Я бы не стал классифицировать это как трюк, более того, мне эта практика кажется вполне оправданной - и именно по этой причине в Delphi введена видимость свойств в пределах юнита, а в C++ это делается с помощью ключевого слова friend. Причина для такого поведения кроется именно в желании скрыть приватные свойства классов и не делать их общедоступными, но в тоже время, тесно связанные классы, такие как элементы и контейнеры, элементы и менеджеры обязательно должны быть осведомлены о внутренних особенностях. Если же этого не делать, то придется состояния объектов открывать, что еще хуже. Поэтому локальное использование видимости в пределах юнита мне кажется меньшим из зол.
"некоторые вызовы функций Windows API оформены как вызовы процедур (не проверяется код возврата)". В некоторых случаях он действительно просто не нужен, но в тех случаях, когда код важен, он всегда проверяется. Хотя, в общем случае, Вы правы.
"нельзя забывать про хэш, который при превышении Capacity может/будет совпадать для разных хэндлов, необходимо сравнивать дополнительно хэндлы и, при необходимости, лезть рекурсивно в Collision". Эта ситуация решается крайне просто назначанием достаточно большого для данной задачи размера массива. Работа с коллизиями - абсолютная неизбежность, которая в данном случае существенно облегчается из-за того, что хеш-коды распределены очень равномерно.
"некрасиво (с моей точки зрения) выглядит многократное использование public в определении нескольких классов - сначала public для конструкторов, потом пошла приватная часть и т.п., а потом опять public". Здесь я могу возразить следующее - мне крайне не нравится упорядоченность имен по алфавиту - я предпочитаю логическую упорядоченность полей, методов и свойств. Кроме того, мне представляется гораздо более логичным размещать код методов в том же порядке, в каком методы объявлены в классе. С этой точки зрения логично определять конструктор и деструктор. Если Borland так не делает - это выбор его программистов, но я буду использовать именно такой порядок деклараций и реализаций, нравится это кому-либо или нет.
"не "по стандарту" производится вызов событий". Здесь вполне можно согласиться, хотя мне и не кажется это удобным, хотя для компонентов это более привычный стиль.Сообщение не подписано |
|
14-04-2005 04:30последний комментарий был мой. |
|
14-04-2005 04:271. Весьма сомнительно использование трюка доступа к приватным свойствам и методам чужого класса в рамках одного юнита. Нужно заботиться о девейлоперах, которым может понадобиться функциональность, которая спрятана в private методах (см. использование TGsvThread.Terminate).
2. некоторые вызовы функций Windows API оформены как вызовы процедур (не проверяется код возврата/успешность вызова).
3. интересно (но спорно!) реализовано хранение ссылок на экземпляры TGsvThread в динамическом массиве. Играться с Capacity невозможно по ходу дела, хотя Collision реализован вполне нормально.
Однако для писателей, которые будут перекрывать менеджер, эта вещь будет представлять собой опасную штучку (простыми словами - геморрой, простите), про которую нельзя забывать (в смысле нельзя забывать про хэш, который при превышении Capacity может/будет совпадать для разных хэндлов, необходимо сравнивать дополнительно хэндлы и, при необходимости, лезть рекурсивно в Collision).
4. некрасиво (с моей точки зрения) выглядит многократное использование public в определении нескольких классов - сначала public для конструкторов, потом пошла приватная часть и т.п., а потом опять public.
5. не "по стандарту" производится вызов событий.
procedure TGsvThreadManager.Release(aHandle: Integer);
..
if (FCount = 0) and Assigned(FOnEmpty) then
FOnEmpty(Self);
...
вместо этого принято вызывать виртуальный protected метод DoEmpty
procedure TGsvThreadManager.DoEmpty;
begin
if Assigned(FOnEmpty) then
FOnEmpty(Self);
end;
а в TGsvThreadManager.Release написать
if (FCount = 0) then
DoEmpty;
Опять же - забота о разработчиках, которые захотят модифицировать менеджер для своих целей.
Резюме:
- для новичков в программировании, этот набор классов, по моему, сложноват.
- а на эдвансед, которые желают унаследоваться и модифицировать функциональность - не достаточно готов к этому.Сообщение не подписано |
|
08-04-2005 09:18сообщение от автора материала Последний проект, в котором я использовал приведенную разработку, представляет собой клиент-серверную систему, в которой удаленные клиенты соединяются с сервером, имеют возможность взаимодействия между собой, и, кроме этого, могут создавать параллельные потоки, реализующие интерпретируемые сценарии, причем другие клиенты могут взаимодействовать с потоками сценариев. То есть, получается около сотни параллельных потоков, которые взаимодействуют между собой. Естественно, что потоки могут завершаться самостоятельно и для сервера никак не допустима ситуация, при которой один поток обратится к уже уничтоженному другому потоку. Сложность состоит в том, что связи потоков исключительно динамические, и инициируются по ходу развития сценариев или при широковещательных взаимодействиях, когда один клиент отправляет сообщения всем или части других клиентов. Система используется для мониторинга процессов и опережающего моделирования возможных ситуаций, протекающих в энергосистеме. Сервер рассчитан на постоянную работу (хотя, конечно, его нельзя отнести к программам реального времени). |
|
08-04-2005 04:20Статья хорошая, мне понравилась, вот бы еще привести ситуации в которых это надо использовать, было бы еще лучще. Я тоже работаю с потоками, но у меня все очень просто и скорость переключений не важна. Пишу ThreadManager только для более удобного слежения за потоками и их визуального наблюдения. Пока сделал цепочку наследников от TThread для базоваго потока и небольшой менаджер. Возможно действительно стоит попробовать переписать уничтожение потоков, у меня поток может уничтожить сам себя и после проинформировать об этом ThreadManager. Я понимаю, что в этой ситуации есть возможность колиции, но кажется она у моей ситуации стремится к нулю. Хотелось бы знать когда стоит применять токой сложный подход? |
|
29-03-2005 02:17> WM_THREAD_COUNTER = WM_USER + 1000;
Лучше использовать в качестве базы WM_APP.
Гарантируется, что значения из диапазона WM_APP (0x8000...0xBFFF) системой точно не используются.
WM_USER - это не значит "юзай, юзер"; это значит, что коды из этого диапазона используются системной user32.dll.
-------------------------------------------------
Как насчёт компонент MTThreading (Managed threading) из библиотеки JEDI (The Initial Developer of the Original Code is Erwin Molendijk. Portions created by Erwin Molendijk are Copyright (C) 2002 Erwin Molendijk)?Сообщение не подписано |
|
25-03-2005 09:17сообщение от автора материала Да, Вы просто немного иначе записали условие проверки произошедшего события. Это, в общем-то, дело вкуса. К потокам это не имеет никакого отношения |
|
25-03-2005 08:39ой! Общибся. Конечно, же
function TGsvSelect.Wait(aTimeout: Cardinal): Boolean;
var
res, i: Cardinal;
begin
res := WaitForMultipleObjects(FCount, @FEvents[0], False, aTimeout);
Result := res > WAIT_OBJECT_0 AND
res < (WAIT_OBJECT_0 + FCount);
if Result then begin
i := res - WAIT_OBJECT_0;
if Assigned(FMethods[i]) then
FMethods[i]();
end;
end;
|
|
25-03-2005 08:37Наверное, потому, что я пока новичек в дельфи, въехал не во все. Буду перечитывать еще раз (когда дочитаю). В целом статья великолепная...
Меня напугали сложностью процессов, я теперь перестраховываюсь, пытаюсь прочитать как можно больше про нюансы, прежде, чем начать писать. Мнегие вещи для меня не очевидны (пока. надеюсь), В том числе такой момент:
function TGsvSelect.Wait(aTimeout: Cardinal): Boolean;
var
res, i: Cardinal;
begin
Result := False;
res := WaitForMultipleObjects(
FCount, @FEvents[0], False, aTimeout);
if res < (WAIT_OBJECT_0 + FCount) then begin
Result := res > WAIT_OBJECT_0;
if Result then begin
i := res - WAIT_OBJECT_0;
if Assigned(FMethods[i]) then
FMethods[i]();
end;
end;
end;
можно ли исправить на
function TGsvSelect.Wait(aTimeout: Cardinal): Boolean;
var
res, i: Cardinal;
begin
res := WaitForMultipleObjects(
FCount, @FEvents[0], False, aTimeout);
if then begin
Result := res > WAIT_OBJECT_0 AND
res < (WAIT_OBJECT_0 + FCount);
if Result then begin
i := res - WAIT_OBJECT_0;
if Assigned(FMethods[i]) then
FMethods[i]();
end;
end;
end;
Если я правильно понимаю, то, что я поправил никак не отразится на данном коде, и не имеет никакого отношения к потокам... (?)
Прошу прощения, если это оффтопик. |
|
|
|