threads & synchronization

6 отговора 0 прегледа
#1

threads & synchronization

Здравейте,
имам нужда от малко помощ за threads и sync.

В момента положението е следното:

1. Опитал съм се да "развържа" UI-то от логиката - т.е. цялата логика е в един "core", който предоставя API, което се извиква от UI-то.
Имам само една инстанция на този core.

2. Имам два singelton-а (communicator и logger), които се ползват от този core съответно за изпращане на данни през нета
и за писане в лога (просто записвам е един файл за да имам инфо за диагностика на евентуални проблеми).

В момента всичко си работи, но има 1 нещо, което ме нервира доста и искам да го променя:
1. При даден action от УИ-то се извиква дадена функция от core-а.
В нормалния случай (от псевдокода по-долу: DoSomething, DoSomethingElse) всичко е ок.
Но при SendSometing() комуникацията може да е доста бавна (1,2,3 секунди) и UI-то ми заспива, което е доста досадно макар и да е за секунди.
Комуникацията е тип request/response - т.е. изпращам request-а и чакам докато дойде response-а:
примерно изпращам GET-заявка към някакъв webserver и получавам response.

Мислех си при core.SendSomething() и core.SendSomethingElse() да стартирам отделен thread,
който да се обръща към communicator-а и чака да дойде response.
През това време УИ-то няма да е замръзнало - само съответния action ще е disabled за известно време, докато thread-а свърши работата. за УИ-то не е фатално, ако резултата от комуникацията се покаже след известно време (в onTerminate на thread-a примерно)

Това което ме притеснява е следното:
Докато се изпълнява thread-а за SendSomething() от УИ-то може да се избере другия (SendSomethingElse),
който ползва същата (единствена) инстанция на communicator.

От друга страна от core-а и communicator-а ползват logger-а
И като са няколко thread-а подозирам, че ще стане мазало.

Ще се радвам, ако дадете едно рамо за това как да си гарантирам, че само един thread ще send-ва в даден момент и ще пише в лога в друг даден момент :)


TMainForm
OnActionDoSomething();
OnActionDoSomethingElse();
OnActionSendSomething();
OnActionSendSomethingElse();
//end

procedure TMainForm.OnActionDoSomething();
begin
core.DoSomething;
end;

procedure TMainForm.OnActionDoSomethingElse();
begin
core.DoSomethingElse;
end;

procedure TMainForm.OnActionSendSomething();
begin
core.SendSomething;
end;

procedure TMainForm.OnActionSendSomethingElse();
begin
core.SendSomethingElse;
end;
=====================================================
TCore
DoSomething();
DoSomethingElse();
SendSomething();
SendSomethingElse();
// end

function TCore.DoSomething(): boolean;
begin
logger.trace("doing something...");
....
logger.trace("done.");
end;

function TCore.DoSomethingElse(): boolean;
begin
logger.trace("doing something else...");
....
logger.trace("done.");
end;

function TCore.SendSomething(): boolean;
begin
logger.trace("sending something else...");

communicator.Send();

logger.trace("done.");
end;

function TCore.SendSomethingElse(): boolean;
begin
logger.trace("sending something else...");

communicator.Send();

logger.trace("done.");
end;
==================================================
TCommunicator
Send();
// end

procedure TCommunicator.Send();
begin
logger.trace('preparing request');
resp := send_data_via_socket(req);
logger.trace('response seceived');
end;

===============================
TLogger
Trace(); ---> adding to log.
// end



Дано да съм бил ясен какво се опитвам да направя, защото половината от решението на даден проблем е доброто му описание ;)

Отговор
в отговор на longy #2

P.S: (threads & synchronization)

и дали стартирането на thread-a да е в core.Send....() или по-надолу в communicator.Send() ?

Отговор
в отговор на longy #3

Re: P.S: (threads & synchronization)

Използвай семафори и мутекси и си защити кода в ядрото с критични секции.

Mixy
Отговор
в отговор на longy #4

Re: threads & synchronization

Един детайл липсва - от какъв характер ти е лог-а.
Ако logger-а е не е свързан с UI:

TLogger

FCS: TCriticalSection;

Trace(); ---> adding to log.

// end

constructor TLogger.Create;
begin
FCS := TCriticalSection.Create;
end;

destructor TLogger.Destroy;
begin
FCS.Free;
end;

procedure TLogger.Trace(const msg : String);
begin
FCS.Enter;
try
// достъп до ресурса (напр. файл)
finally
FCS.Leave;
end;
end;


Ако logger-а update-ва UI (напр. Memo), вече зависи как ще реализираш конците.
После пак ще пиша, че сега нямам време.
Междувременно req и resp глобални променливи ли са или са локални за TCommunicator.Send?
Също в TCommunicator.Send прави ли се нещо с отговора или само се предава нагоре? Сигурно обработката става в TCore след communicator.Send();?

Отговор
в отговор на Formal #5

Re: threads & synchronization

Благодаря за отговорите до момента
- loggera не е свързан с УИ-то.
- req и resp са в core, а communitor-а от req прави малко подготовка на един "raw" req , който изпраща. и после от "raw" resp подготвя resp, който връща нагоре към core.


ето тук съм копирал промените, които направих (в зелено) спрямо началния вариант - видимо работи, но моля хвърлете едно око да не би да съм написал някоя голяма глупост дето не виждам.



=====================================================
TCore
DoSomething();
DoSomethingElse();
SendSomething();
SendSomethingElse();

// events
OnSomethingSent();
OnSomethingElseSent();

// end


function TCore.SendSomething(): boolean;
begin
logger.trace("sending something else...");

communicator.Send(@OnSomethingSent);

logger.trace("done.");
end;

function TCore.SendSomethingElse(): boolean;
begin
logger.trace("sending something else...");

communicator.Send(@OnSomethingElseSent);

logger.trace("done.");
end;
==================================================
TCommunicator
Send();
commLock: TCriticalSection; - създавам я в Create на communicator и я изчиствам когато го Free-вам
end;

procedure TCommunicator.Send(eventSent);
var
thr: TCommunicatingThread;
begin
thr := TCommunicatingThread.Create();
thr.OnTerminate := eventSent;
end;

TCommunicatingThread = class(TThread)

procedure TCommunicatingThread.Execute;
begin
logger.trace('preparing request');

commLock.Enter;

logger.trace('sending');
resp := send_data_via_socket(req);
logger.trace('sent');

commLock.Leave;

logger.trace('response seceived');
end;


==============================
TLogger
Trace();
logLock: TCriticalSection; - създавам я в Create на logger и я изчиствам когато го Free-вам
// end



procedure TLogger.Trace();
var
thr: TLoggingThread;
begin
thr := TLoggingThread.Create();
end;

TLoggingThread = class(TThread)

procedure TLoggingThread.Execute;
begin
logLock.Enter;

append_to_logfile();

logLock.Leave;
end;




Отговор
в отговор на longy #6

Re: threads & synchronization

На мен ми изглежда OK така, със следните добавки:

function TCore.SendSomething(): boolean;
begin
logger.trace("sending something else...");
communicator.Send(@OnSomethingSent);
logger.trace("done."); // за съжаление, това ще се изписва
// преди да е приключила комуникацията -
// може би ще искаш да го преместиш някъде другаде

end;

==================================================

TCommunicator
Send();
commLock: TCriticalSection; - създавам я в Create на communicator и я изчиствам когато го Free-вам
end;


procedure TCommunicator.Send(eventSent);
var
thr: TCommunicatingThread;
begin
thr := TCommunicatingThread.Create(true); // suspended, за да можеш да си сигурен,
// че следващият ред ще се изпълни
thr.FreeOnTerminate := true; // иначе ще имаш memory leak

thr.OnTerminate := eventSent;
thr.Resume;
end;

TCommunicatingThread = class(TThread)

procedure TCommunicatingThread.Execute;
begin
logger.trace('preparing request');
commLock.Enter;
try
logger.trace('sending');
resp := send_data_via_socket(req);
logger.trace('sent');
finally // това е необходимо, защото, ако send_data_via_socket или logger.trace
// вдигне exception, критичната секция остава заключена
// и няма да можеш да пращаш нищо повече,
// докато не се рестартира програмата.

commLock.Leave;
end;
logger.trace('response seceived');
end;

Освен това в logger-а е напълно излишно да създаваш thread, достатъчно е просто да заградиш писането във файла с критична секция, както в предния ми пост.
С req и resp не би трябвало да имаш проблеми така, както е направено, защото OnTerminate на Thread-а се изпълява в контекста на главната нишка. Просто прегледай още веднъж за всеки случай, дали има вероятност 2 нишки да пипат в един и същи обект - от псевдо кода няма как де се види това, трябва реалният код.

Редактирано от Formal на

Отговор
в отговор на Formal #7

Re: threads & synchronization


extra,
мерси много :)
мисля, че това ми е предостатъчно и би трябвало да се оправя нататък

Отговор