Модераторы: Snowy, bartram, MetalFan, bems, Poseidon, Riply
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Вопрос по правильной синхронизации, как правильно синхронизировать субпоток 
:(
    Опции темы
yogin
Дата 3.10.2012, 10:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 88
Регистрация: 23.12.2007

Репутация: нет
Всего: нет



Приветствую!

Имеется такое взаимодействие потоков:
  
  MainVCLThread   ---(создаёт экземпляр класса)-->  TSomeClass  --(создаёт экзепляр класса-потока)-->  TSomeThread.
  В классе TSomeClass есть событие OnData(); такое же событие есть и в TSomeThread.
                                                                                      
Поток обрабатывает клиентский сокет, дак вот когда поток считывает с сокета очередные данные, он должен дать их главному потоку VCL, для этого я и сделал собятия OnData - по ним как по цепочке данные передаются в главный поток.
(кстати, попутный вопрос: можно ли оптимальнее сделать эту "передачу по цепочке", т.к. эта цепочка может состоять не только из 2 классов, а больше, тогда можно запариться дублировать события в каждом классе только потому, что самому вложенному надо что-то передать самому верхнему классу, или такая архитектура нормальная?)

Сейчас я в потоке вызываю synchronize(DoEvent) и всё работает.

Но вопрос в том, как этот вызов синхронайза заменить на что-нибудь, чтобы классы по обработке данных с сокета можно ыбло переносить и использовать как в VCL приложениях, так и в консольных?
Я склоняюсь к тому, что надо заменить это критическими секциями(если это не так - критСекции не подходят, то внимательно слушаю вариант), но куда прописать критСекции, где прописать входы и т.п. вот в этом хочу разобраться.
PM MAIL   Вверх
kami
Дата 3.10.2012, 10:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1806
Регистрация: 25.8.2007
Где: Санкт-Петербург

Репутация: 15
Всего: 72



Цитата(yogin @  3.10.2012,  10:16 Найти цитируемый пост)
как этот вызов синхронайза заменить на что-нибудь, чтобы классы по обработке данных с сокета можно ыбло переносить и использовать как в VCL приложениях, так и в консольных?

А какая разница, консольное - не консольное? Окна-то все равно создавать можно. Только если консольное, нужно самому организовывать цикл выборки сообщений в главном потоке. Ну а для дополнительных - это по необходимости.
Syncronize - нормальный выход. По крайней мере, если не нужно передавать доп.параметры. Он гарантирует, что метод вызовется в главном потоке (или в последних версиях - в контексте родительского потока? Но в данном случае это не важно).
Критические секции - тоже выход. Но не нужно забывать, что в этом случае вызываемый метод будет работать в контексте своего потока. А для VCL это крайне нежелательно и чревато глюками.

У меня похожая ситуация, тоже с сокетами. Сделано так:
доп.поток при получении информации из сокета шлет PostMessage в окно родительского экземпляра класса (Post - чтобы не прерываться, но если нужно "принял и сразу обработал" - тогда SendMessage).
Родительский класс в оконной процедуре считывает у потока данные.
При этом методы типа GetDataFromThreadBuf (которым родитель счиывает данные из потока) и AddReceivedDataToThreadBuf (которым сам поток добавляет данные из сокета) внутри обрамлены критической секцией. Она одна и создается потоком.
Таким образом, класс-родитель работает в своем потоке, ничего не знает и знать не хочет о синхронизации, всем этим рулит поток. Все довольны, все смеются.

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

Добавлено через 2 минуты и 31 секунду
Кстати, при использовании вместо Synchronize - Post(Send)Message можно передавать параметры, что может быть немаловажно...
PM MAIL WWW   Вверх
yogin
Дата 3.10.2012, 11:59 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 88
Регистрация: 23.12.2007

Репутация: нет
Всего: нет



Цитата(kami @  3.10.2012,  10:44 Найти цитируемый пост)
А какая разница, консольное - не консольное?

в консольном приложении этот же код встанет(зависнит) на вызове synchronize, т.к. VCL-ом и не пахнет.
Цитата(kami @  3.10.2012,  10:44 Найти цитируемый пост)
Окна-то все равно создавать можно.

Вот именно что мне этого и не нужно в некоторых случаях, например при разработке консольного сетевого клиента или когда принимающий сетевой код находится в DLL. В последнем случае даже если DLL подгружается VCL приложением, то синхронайз в длл зависает. Такую проблему я решил как бы не синхронно - просто выызваю событие, которое идёт потом в приложение.
Цитата(kami @  3.10.2012,  10:44 Найти цитируемый пост)
Критические секции - тоже выход. Но не нужно забывать, что в этом случае вызываемый метод будет работать в контексте своего потока. А для VCL это крайне нежелательно и чревато глюками.

Вот меня именно этот фактор-то и волнует...
Цитата(kami @  3.10.2012,  10:44 Найти цитируемый пост)
доп.поток при получении информации из сокета шлет PostMessage в окно родительского экземпляра класса (Post - чтобы не прерываться, но если нужно "принял и сразу обработал" - тогда SendMessage).
Родительский класс в оконной процедуре считывает у потока данные.
При этом методы типа GetDataFromThreadBuf (которым родитель счиывает данные из потока) и AddReceivedDataToThreadBuf (которым сам поток добавляет данные из сокета) внутри обрамлены критической секцией. Она одна и создается потоком.
Таким образом, класс-родитель работает в своем потоке, ничего не знает и знать не хочет о синхронизации, всем этим рулит поток. Все довольны, все смеются.

Нормальная архитектура, у меня тоже до синхронайза была такая. Просто в новом варианте я стремился сделать всё только на CS, чтобы не делать две версии сетевого клиента(для VCL и не для VCL), но по всей видимости без этого никак... Т.к. консоле постмессагу тоже не отправить.
А если выбирать между synchronize и мессагами, то архитектура мессаг мне больше симпатизирует. Я думаю можно поискать как "красиво" посылать синхронные команды консольному процессу.
Цитата(kami @  3.10.2012,  10:44 Найти цитируемый пост)
По поводу транзитных событий - может, это и некрасиво, зато (имхо) правильно - для класса все внутренности других классов должны быть черным ящиком, в который лезть не нужно.

Полностью солидарен, ну т.е. меня не отпугивает вся эта цепь передач, я просто так поставил вопрос.
А вопросом таким задался т.к. пришёл к такой концепции опытом, поэтому как бы задался вопросом "может быть есть что-то оптимальнее, до чего я пока недопёр?...".
Цитата(kami @  3.10.2012,  10:44 Найти цитируемый пост)
Кстати, при использовании вместо Synchronize - Post(Send)Message можно передавать параметры

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

type
  TThCliSockTCPNBEventData = procedure(aSender: TObject; const aDataLen: integer; var aData: TArrOfByte) of object;
...
TThCliSockTCPNB = class(TThread)
  private
    ...
    {$REGION ' приём пакетов '}
    fRecvBuf  : TArrRecvBuf; // общий буфер для получения данных
    fRecvRes  : integer; // для метода события
    fRecvData : TArrOfByte; // фактические принятые данные, для события
    {$ENDREGION}
  private
    {$REGION ' события '}
    fErrStr   : string;
    fOnSockData : TThCliSockTCPNBEventData; // приняты данные
    fOnClose    : TThCliSockTCPNBEventClose; // сервер закрыл соединение
    fOnError    : TThCliSockTCPNBEventError; // ошибка
    // методы вызовов событий должны быть для Syncronize а т.е. без атрибутов
    procedure DoEventData;//(const aDataLen: integer; var aRecvData: TArrOfByte);
    procedure DoEventClose;
    procedure DoEventError;//(const aErrStr: string);
    {$ENDREGION}
     ...
  public
    ...
    property OnSockData: TThCliSockTCPNBEventData read fOnSockData write fOnSockData;
    property OnClose: TThCliSockTCPNBEventClose read fOnClose write fOnClose;
    property OnError: TThCliSockTCPNBEventError read fOnError write fOnError;
  end;

...

procedure TThCliSockTCPNB.DoEventData;//(const aDataLen: integer; var aRecvData: TArrOfByte);
begin
  if Assigned(fOnSockData) then
    fOnSockData(Self, fRecvRes, fRecvData);
end;

procedure TThCliSockTCPNB.DoEventClose;
begin
  if Assigned(fOnClose) then
    fOnClose(Self);  
end;

procedure TThCliSockTCPNB.DoEventError;//(const aErrStr: string);
begin
  if Assigned(fOnError) then
    fOnError(Self, fErrStr);
end;

procedure TThCliSockTCPNB.Execute;
begin
  repeat
    ...
        // приём
        fRecvRes := recv(fSocket, fRecvBuf[0], c_RECV_BUF_SIZE, 0);
        if fRecvRes > 0 then
        begin // приём успешен
          SetLength(fRecvData, fRecvRes);
          move(fRecvBuf[0], fRecvData[0], fRecvRes);
          Synchronize(DoEventData);
        end
        else if fRecvRes = 0 then
        begin // сервер закрыл соединение
          Synchronize(DoEventClose);
        end // ошибка
        else if WSAGetLastError <> WSAEWOULDBLOCK then
        begin
          fErrStr := 'ошибка при получении данных от клиента: ' + suGetLastErrorString;
          Synchronize(DoEventError);
        end;
    ...
  until fCmdTerminate;
end;

PM MAIL   Вверх
Alexeis
Дата 3.10.2012, 12:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 16
Всего: 459



  Есть еще такое решение, в OnIdle главного потока делать ожидание на MsgWaitForMultipleObjects . Соответственно события от сокетов обрабатывать в главном потоке не вешая его. Вообще, использование второго потока оправданно, когда требуется ждать чего-то или же нагрузка на проц столь велика, что ее неплохо бы распределить на разные ядра. Если же у нас своя очередь обработки сообщений, так даже OnIdle не нужен, а вместо GetMessage использовать MsgWaitForMultipleObjects, соответственно все операции делать в одном главном потоке асинхронно. Очень удобно. Для асинхронных операций использование потоков часто нецелесообразно.


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
yogin
Дата 3.10.2012, 12:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 88
Регистрация: 23.12.2007

Репутация: нет
Всего: нет



Цитата(Alexeis @  3.10.2012,  12:05 Найти цитируемый пост)
Вообще, использование второго потока оправданно, когда требуется ждать чего-то или же нагрузка на проц столь велика, что ее неплохо бы распределить на разные ядра.

Я сделал для клиентского сокета отдельную нить потому что вызов метода TimerProcess() приходилось тянуть из главной нити по цепочке классов-менеджеров сокета, которые "стоят" до класса непосредственного чтения буфера сокета. Хотя их всего два... А т.е. в этом небыло острой необходимости или, можно сказать, мотивация - удобство.
Или например, если сетевой код в DLL, то надо было бы создавать експортируемый метод dllTimerProcess() и обязывать, например стороннего, разработчика его вызывать, а иначе с сокета ничего не аукнится...
Ещё вероятно я просто напросто имею чрезмерный идеалистический взор на архитектуру...
PM MAIL   Вверх
Alexeis
Дата 3.10.2012, 12:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 16
Всего: 459



  При асинхронной работе количество сокетов не имеет значения. Сетевая карта, то одна и скорее всего аппаратно может работать только последовательно, так что в отсутствии ожидания отдельный поток никак не улучшает работу. Я также пользовался отдельным потоком для сокетов, в плагинах, но там я все задачи закрывал в этом потоке. Просто для универсальности. Если один плагин косячит, чтобы не вешал единственный рабочий поток. 


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Чучмек
Дата 3.10.2012, 14:39 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


НЭТ БИЛЭТ
**


Профиль
Группа: Участник
Сообщений: 841
Регистрация: 11.5.2008
Где: СССР

Репутация: 7
Всего: 41



А почемубы не реализовать свой Synchronize
Код

unit Unit1;

interface
uses windows,classes;

type
 tmetod=procedure of object;

 TMyThread=class(tthread)
  class procedure Synchronize(metod:tmetod);
  end;
 procedure CallThreadSynchrProc;
 function WaitThreadSynchrProc(dwMilliseconds:cardinal):cardinal;
implementation


type
 TThreadEv=record
  metod:tmetod;
  event:cardinal;
  end;
 TThreadEvList=class
  private
   MThreadEv:array of TThreadEv;
   FCount:integer;
   function GetRec(ind:integer):TThreadEv;
  public
   property Items[ind:integer]:TThreadEv read GetRec;default;
   property Count:integer read FCount;
   procedure Add(metod:tmetod;event:cardinal);
   procedure Clear;
   constructor Create;
   destructor Destroy;
  end;

var
 cs:TRTLCriticalSection;
 ThreadEvList:TThreadEvList;
 spev:cardinal;
{ TMyThread }
class procedure TMyThread.Synchronize(metod: tmetod);
var ev:cardinal;
begin
EnterCriticalSection(cs);
ev:=createevent(nil,false,false,nil);
ThreadEvList.Add(metod,ev);
SetEvent(spev);
LeaveCriticalSection(cs);
WaitForSingleObject(ev,INFINITE);
CloseHandle(ev);
end;

{ TThreadEvList }

procedure TThreadEvList.Add(metod: tmetod; event: cardinal);
begin
if FCount=length(MThreadEv) then  SetLength(MThreadEv,length(MThreadEv)+128);
MThreadEv[FCount].metod:=metod;
MThreadEv[FCount].event:=event;
inc(FCount);
end;

procedure TThreadEvList.Clear;
begin
FCount:=0;
end;

constructor TThreadEvList.Create;
begin
FCount:=0;
end;

destructor TThreadEvList.Destroy;
begin
MThreadEv:=nil;
end;

function TThreadEvList.GetRec(ind: integer): TThreadEv;
begin
result:=MThreadEv[ind];
end;

procedure CallThreadSynchrProc;
var i:integer;
begin
EnterCriticalSection(cs); 
for i:=0 to ThreadEvList.count-1 do
 begin
 ThreadEvList[i].metod;
 SetEvent(ThreadEvList[i].event);
 end;
ThreadEvList.clear;
ResetEvent(spev);
LeaveCriticalSection(cs);
end;

function WaitThreadSynchrProc(dwMilliseconds:cardinal):cardinal;
begin
result:=WaitForSingleObject(spev,dwMilliseconds);
end;

initialization
spev:=CreateEvent(nil,true,false,nil);
ThreadEvList:=TThreadEvList.Create;
InitializeCriticalSection(cs);

finalization
DeleteCriticalSection(cs);
CloseHandle(spev);
end.

Код

program Project2;

{$APPTYPE CONSOLE}

uses
  windows,
  Unit1 ;


type
 tmt=class(TMyThread)
  fs:string;
  ft:cardinal;
  procedure  execute; override;
  procedure p;
  constructor Create(s:string;t:cardinal);
 end;


{ tmt }
procedure tmt.execute;
begin
while true do
 begin
 sleep(ft);
 Synchronize(p);
 end;
end;

procedure tmt.p;
begin
 writeln(fs);
end;

constructor tmt.Create(s:string;t:cardinal);
begin
fs:=s;
ft:=t;
inherited  Create(false);
end;

begin
tmt.Create('1',3000);
tmt.Create('2',3500);
tmt.Create('3',4000);  
while true do
 begin
 WaitThreadSynchrProc(INFINITE);
 CallThreadSynchrProc;
 end;
end.



--------------------
умную мысль держи при себе, а дурной - поделись с другими 
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: WinAPI и системное программирование"
Snowybartram
MetalFanbems
PoseidonRrader
Riply

Запрещено:

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Delphi обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи
  • 99% ответов по WinAPI можно найти в MSDN Library, оставшиеся 1% здесь

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, bartram, MetalFan, bems, Poseidon, Rrader, Riply.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: WinAPI и системное программирование | Следующая тема »


 




[ Время генерации скрипта: 0.0745 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.