My idea is to use TDictionary to manage client connections on IdTCPServer. Here is a simple example code (not tested) for understanding purposes:
var
Dic: TDictionary<string, TIdContext>;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
Dic := TDictionary<string, TIdContext>.Create;
end;
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
Dic.Free;
end;
procedure TfrmMain.TCPServerConnect(AContext: TIdContext);
var
Hostname: string;
begin
Hostname := UpperCase(GStack.HostByAddress(AContext.Binding.PeerIP));
if not Dic.ContainsKey(Hostname) then Dic.Add(Hostname, AContext);
end;
procedure TfrmMain.TCPServerDisconnect(AContext: TIdContext);
var
Hostname: string;
begin
Hostname := UpperCase(GStack.HostByAddress(AContext.Binding.PeerIP));
if Dic.ContainsKey(Hostname) then
begin
Dic[Hostname].Free;
Dic.Remove(Hostname);
end;
end;
Is this code thread safe?
In a word: No.
If you inspect the source of TDictionary you should quickly realise that there is no provision for thread-safety in the implementation itself. Even if it were, by having discrete calls to a Dic instance you have potential race conditions to contend with:
if Dic.ContainsKey(Hostname) then
begin
// In theory the Hostname key may be removed by another thread before you
// get a chance to do this : ...
Dic[Hostname].Free;
Dic.Remove(Hostname);
end;
You need to make your own use of Dic thread safe, and fortunately in this sort of example this is easily achieved using a monitor on the object itself:
MonitorEnter(Dic);
try
if not Dic.ContainsKey(Hostname) then
Dic.Add(Hostname, AContext);
finally
MonitorExit(Dic);
end;
// ....
MonitorEnter(Dic);
try
if Dic.ContainsKey(Hostname) then
begin
Dic[Hostname].Free;
Dic.Remove(Hostname);
end;
finally
MonitorExit(Dic);
end;
If you are not familiar with monitors in Delphi, in simple terms you can think of a monitor as a ready-to-use critical section supported by every TObject descendant (in older versions of Delphi which did not support these monitors you could have achieved the same thing with an explicit critical section).
To answer your specific question - no, TDictionary is NOT thread-safe, so you must protect access to it.
Your code is not handling the possibility of multiple clients behind a proxy/router connecting to the same server. They will all have the same PeerIP and HostName values. Those values are not unique enough by themselves to identify clients. You need to create your own unique identifiers, for instance by having your clients login to your server with a username, and then use that as your dictionary key instead.
And lastly, DO NOT free TIdContext objects! They are owned by TIdTCPServer and will be freed automatically after the OnDisconnect event handler has exited.
Related
I've currently developing a program which executes some AT commands periodically.
I tried to make it multi-threaded because this program should work with 8 GSM modems concurrently.
here is my extended TThread class as TWorkerThread:
TWorkerThread = class(TThread)
private
FThreadJob : TThreadJobs;
FSimNum : Word;
FZylGSM : TZylGSM;
SL_AT : TStringList;
FSignalGauge : TsGauge;
procedure SyncProc;
public
TerminateThread : Boolean;
constructor Create;
property ThreadJob : TThreadJobs read FThreadJob write FThreadJob;
property ZylGSM : TZylGSM read FZylGSM write FZylGSM;
property SimNum : Word read FSimNum write FSimNum;
property SignalGauge: TsGauge read FSignalGauge write FSignalGauge;
protected
procedure Execute; override;
end;
And body of my thread's methods :
constructor TWorkerThread.Create;
begin
inherited Create(True);
if Not Assigned(SL_AT) then SL_AT := TStringList.Create;
SL_AT.Clear;
FThreadJob := tjNone;
TerminateThread := False;
FreeOnTerminate := True;
end;
procedure TWorkerThread.Execute;
begin
inherited;
if FThreadJob = tjNone then Exit;
while TerminateThread=False do Synchronize(SyncProc);
end;
procedure TWorkerThread.SyncProc;
var
ts : String;
SignalStrength : Byte;
begin
if bTerminateFlag then TerminateThread := True;
if TerminateThread then Exit;
case FThreadJob of
tjOperatorName : ;
tjSignalQuality :
begin
FZylGSM.ExecuteATCommand('AT+CSQ', SL_AT);
if (SL_AT.Count>2) And (Pos('OK', SL_AT[2])>0) then begin
ts := Copy(SL_AT[1], Pos(':', SL_AT[1])+1, Length(SL_AT[1]));
ts := Trim(ts);
if ts = '99' then ts:='0';
SignalStrength := StrToIntDef(ts, 0);
SignalGauge.Progress := SignalStrength;
end;
if bTerminateFlag then TerminateThread := True;
// Application.ProcessMessages;
end;
end;
end;
I've used a for loop to create 8 threads like this:
DevPorts.GSM_Ports[i].WorkerThread := TWorkerThread.Create;
DevPorts.GSM_Ports[i].WorkerThread.ThreadJob := tjSignalQuality;
DevPorts.GSM_Ports[i].WorkerThread.SimNum := i+1;
DevPorts.GSM_Ports[i].WorkerThread.SignalGauge := FindComponent('Sig_'+IntToStr(i)) as TsGauge;
DevPorts.GSM_Ports[i].WorkerThread.ZylGSM := DevPorts.GSM_Ports[i].Comm;
DevPorts.GSM_Ports[i].WorkerThread.Start;
The program worked as expected when I commented "Application.ProcessMessages", the problem is when I use "Application.ProcessMessages" in "TWorkerThread.SyncProc", some of my threads don't executes. I know it could be wrong to use ProcessMessage in the thread function but I did it because the Main GUI thread hangs up during send/recv of threads.
Any help will be appreciated.
Do not call Application.ProcessMessages from your threads. This is a terrible thing to do. The best you can hope for is spectacular failures. It calls code that should be run on the main thread on the wrong threads.
The reason your main GUI thread hangs is because you're not running anything multi-threaded. The line while TerminateThread=False do Synchronize(SyncProc); is synchronising everything to run back on the main thread. So currently your threads are pointless.
The purpose of Synchronize() is to allow threads to coordinate access to shared data so you don't have to deal with race conditions. However, the ideal is to share as little data as possible so that your threads can work independently of each other (and the main thread), without having to worry values under its control being changed at inappropriate times.
So alarm bells are screaming when you make most of your worker thread's members public:
public
TerminateThread : Boolean;
constructor Create;
property ThreadJob : TThreadJobs read FThreadJob write FThreadJob;
property ZylGSM : TZylGSM read FZylGSM write FZylGSM;
property SimNum : Word read FSimNum write FSimNum;
property SignalGauge: TsGauge read FSignalGauge write FSignalGauge;
You need to reevaluate what the responsibilities of your worker threads are and encapsulate that work appropriately. (Only call Synchronize() for code that should be synchronised!) However, I'm not familiar with the components you're using, and you may find that they're poorly written and not suitable for multi-threading as a result.
Other problems
Apart from the glaring immediate problems you have. There are also a number of mistakes demonstrating gaps in your understanding of multi-threaded development.
Do not call inherited from TWorkerThread.Execute. The ancestor method is abstract - has no implementation an cannot be called. And even though the Delphi compiler generously protects you from your mistake, it's a mistake nonetheless.
Your implementation of TerminateThread : Boolean; replicates existing functionality built into TThread. Instead of reinventing the wheel, use what Delphi has already provided.
I cannot see where you declared or are setting bTerminateFlag. My hunch is it's global. Using globals with multiple threads is like juggling flaming torches while standing in a room filled with open barrels of gunpowder.
Some guesses
I can hazard some guesses based on what you are trying to do in the code you have shown.
It looks like you're updating TsGuage instances to visually indicate the signal strength for each device. This is a GUI update that must be synchronised.
The line FZylGSM.ExecuteATCommand('AT+CSQ', SL_AT); seems to be the place where you interact with the device. It's probably also the slowest and what you want to processes off the main thread. This should not be synchronised if possible. However, it as indicated earlier, the feasibility of doing so depends on the implementation of that component.
That said, it seems the only line you should be synchronising is: SignalGauge.Progress := SignalStrength;.
I've built a simple logging class and want to confirm that it is thread safe. Basically the Log, RegisterLogger and UnRegisterLogger will be called from different threads. Log will be called alot (from many different threads) and RegisterLogger and UnRegisterLogger infrequently.
Basically my question can be boiled down to is: "Are reads on TList<x> thread safe?", that is to say can I have multiple threads accessing a TList at the same time.
IExecutionCounterLogger is an interface with a Log method (with the same signature as TExecutionCounterServer.Log)
Type
TExecutionCounterServer = class
private
Loggers : TList<IExecutionCounterLogger>;
Synchronizer : TMultiReadExclusiveWriteSynchronizer;
public
procedure RegisterLogger(Logger : IExecutionCounterLogger);
procedure UnRegisterLogger(Logger : IExecutionCounterLogger);
procedure Log(const ClassName, MethodName : string; ExecutionTime_ms : integer);
constructor Create;
destructor Destroy; override;
end;
constructor TExecutionCounterServer.Create;
begin
Loggers := TList<IExecutionCounterLogger>.Create;
Synchronizer := TMultiReadExclusiveWriteSynchronizer.Create;
end;
destructor TExecutionCounterServer.Destroy;
begin
Loggers.Free;
Synchronizer.Free;
inherited;
end;
procedure TExecutionCounterServer.Log(const ClassName, MethodName: string; ExecutionTime_ms: integer);
var
Logger: IExecutionCounterLogger;
begin
Synchronizer.BeginRead;
try
for Logger in Loggers do
Logger.Log(ClassName, MethodName, ExecutionTime_ms);
finally
Synchronizer.EndRead;
end;
end;
procedure TExecutionCounterServer.RegisterLogger(Logger: IExecutionCounterLogger);
begin
Synchronizer.BeginWrite;
try
Loggers.Add(Logger);
finally
Synchronizer.EndWrite;
end;
end;
procedure TExecutionCounterServer.UnRegisterLogger(Logger: IExecutionCounterLogger);
var
i : integer;
begin
Synchronizer.BeginWrite;
try
i := Loggers.IndexOf(Logger);
if i = -1 then
raise Exception.Create('Logger not present');
Loggers.Delete(i);
finally
Synchronizer.EndWrite;
end;
end;
As a bit more background, this is a follow on from this question. Basically I've added some instrumentation to every method of a (DCOM) DataSnap server, also I've hooked into every TDataSnapProvider OnGetData and OnUpdateData event.
Are reads on TList<T> thread safe? That is to say can I have multiple threads accessing a TList<T> at the same time?
That is thread safe and needs no synchronisation. Multiple threads can safely read concurrently. That is equivalent to (and in fact implemented as) reading from an array. It is only if one of your threads modifies the list that synchronisation is needed.
Your code is a little more complex than this scenario. You do appear to need to cater for threads modifying the list. But you've done so with TMultiReadExclusiveWriteSynchronizer which is a perfectly good solution. It allows multiple reads threads to operate concurrently, but any write threads are serialized with respect to all other threads.
Emphasizing the first part of your question, you state that calls to RegisterLogger and UnregisterLogger are infrequently. While the Log call is only reading the list, these other two are changing the list. In this case you have to make sure that none of these is executed while a Log call is executing or may occur.
Imagine a Delete in UnregisterLogger is executed during the for loop in Log. The results are unpredictable at least.
It will be not sufficient to use the Synchronizer only in those two writing calls.
So the answer to your question
Are reads on TList thread safe?
can only be: it depends!
If you can make sure that no RegisterLogger and UnregisterLogger happen (i.e. only read calls can happen), you can safely omit the Synchronizer. Otherwise - better not.
I would like to have a thread running in background which will check connection to some server with given time interval. For example for every 5 seconds.
I don't know if there is a good "desing pattern" for this? If I remember corretly, I've read somewehere that sleeping thread in its execute method is not good. But I might be wrong.
Also, I could use normal TThread class or OTL threading library.
Any ideas?
Thanks.
In OmniThreadLibrary, you would do:
uses
OtlTask,
OtlTaskControl;
type
TTimedTask = class(TOmniWorker)
public
procedure Timer1;
end;
var
FTask: IOmniTaskControl;
procedure StartTaskClick;
begin
FTask := CreateTask(TTimedTask.Create())
.SetTimer(1, 5*1000, #TTimedTask.Timer1)
.Run;
end;
procedure StopTaskClick;
begin
FTask.Terminate;
FTask := nil;
end;
procedure TTimedTask.Timer1;
begin
// this is triggered every 5 seconds
end;
As for sleeping in Execute - it depends on how you do it. If you use Sleep, then this might not be very wise (for example because it would prevent the thread to stop during the sleep). Sleeping with WaitForSingleObject is fine.
An example of TThread and WaitForSingleObject:
type
TTimedThread = class(TThread)
public
procedure Execute; override;
end;
var
FStopThread: THandle;
FThread: TTimedThread;
procedure StartTaskClick(Sender: TObject);
begin
FStopThread := CreateEvent(nil, false, false, nil);
FThread := TTimedThread.Create;
end;
procedure StopTaskClick(Sender: TObject);
begin
SetEvent(FStopThread);
FThread.Terminate;
FThread.Free;
CloseHandle(FStopThread);
end;
{ TTimedThread }
procedure TTimedThread.Execute;
begin
while WaitForSingleObject(Form71.FStopThread, 5*1000) = WAIT_TIMEOUT do begin
// this is triggered every 5 seconds
end;
end;
OTL timer implementation is similar to the TThread code above. OTL timers are kept in priority list (basically the timers are sorted on the "next occurence" time) and internal MsgWaitForMultipleObjects dispatcher in TOmniWorker specifies the appropriate timeout value for the highest-priority timer.
You could use an event and implement the Execute method of the TThread descendant by a loop with WaitForSingleObject waiting for the event, specifying the timeout. That way you can wake the thread up immediately when needed, e.g. when terminating.
If the thread runs for the life of the app, can be simply terminated by the OS on app close and does not need accurate timing, why bother with solutions that require more typing than sleep(5000)?
To add another means of achieving a 5-sec event it is possible to use the Multimedia Timer which is similar to TTimer but has no dependence on your application. After configuring it (you can setup one-shot or repetitive) it calls you back in another thread. By its nature it is very accurate (to within better than 1ms). See some sample Delphi code here.
The code to call the timer is simple and it is supported on all Windows platforms.
Use CreateWaitableTimer and SetWaitableTimer
I am experimenting with multithreading in Delphi (XE) and have run into a problem with the use of a Global Variable between the main VCL thread and a second work thread.
My project involves a 2nd worker thread that scans through some files, and updates a globalvar string with the current filename its on. This globalvar is then picked up via a timer on the main VCL thread, and updates a statusbar.
I have noticed though that it occasionally comes up with a 'Invalid Pointer Operation'...or 'Out of Memory' or the work thread just stops responding (deadlock probably).
I therefore created a test app to identify and greatly increase the chance of error so i could see what's going on.
type
TSyncThread = class(TThread)
protected
procedure Execute; override;
end;
var
Form11: TForm11;
ProgressString : String;
ProgressCount : Int64;
SyncThread : TSyncThread;
CritSect : TRTLCriticalSection;
implementation
{$R *.dfm}
procedure TForm11.StartButtonClick(Sender: TObject);
begin
Timer1.Enabled := true;
SyncThread := TSyncThread.Create(True);
SyncThread.Start;
end;
procedure TForm11.StopbuttonClick(Sender: TObject);
begin
Timer1.Enabled := false;
SyncThread.Terminate;
end;
procedure TForm11.Timer1Timer(Sender: TObject);
begin
StatusBar1.Panels[0].Text := 'Count: ' + IntToStr(ProgressCount);
StatusBar1.Panels[1].Text := ProgressString;
end;
procedure TSyncThread.Execute;
var
i : Int64;
begin
i := 0;
while not Terminated do begin
inc(i);
EnterCriticalSection(CritSect);
ProgressString := IntToStr(i);
ProgressCount := i;
LeaveCriticalSection(CritSect);
end;
end;
initialization
InitializeCriticalSection(CritSect);
finalization
DeleteCriticalSection(CritSect);
I set the timer interval to 10ms so that it is reading a lot, whilst the worker thread is running flat out updating the global var string. Sure enough this app barely lasts a second when run before it comes up with the above errors.
My question is, does the read operation of the Global var in the VCL Timer need to be run in a critical section? - if so, why?. From my understanding it is only a read, and with the writes already running in a critical section, i cannot see why it runs into a problem. If i do put the read in the timer into a critical section as well - it works fine....but im unhappy just doing that without knowing why!
I am new to multithreading so would appreciate any help in explaining why this simple example causes all sorts of problems and if there is a better way to be accessing a string from a worker thread.
Delphi String is allocated on a heap, it is not a static buffer somewhere. The variable itself is just a pointer. When your reading thread accesses a String, and at the same time this very string is being deallocated by another thread, bad things happen. You are accessing already freed memory, possibly allocated again for something else, etc.
Even if this String was a static buffer, update operations are not atomic, therefore you could be using a corrupted string that is being updated at this very moment (half new data and half old).
So you need to protect your reading operations with the same critical section you used around the writing operations.
The app is written in Delphi XE.
I have two classes, a TBoss and TWorker, which are both based of of TThread.
The TBoss is a single instance thread, which starts up and then will create about 20 TWorker threads.
When the boss creates a instance of TWorker it assigns it a method to call synchronize on, when the Worker has finished with what it's doing it calls this method which allows the Boss to access a record on the Worker.
However I feel this is a problem, calling synchronize appears to be locking up the whole application - blocking the main (ui) thread. Really it should just be synchronizing that worker to the boss thread....
Previously I used messages/packed records to send content between threads which worked well. However doing it this way is much cleaner and nicer.... just very blocking.
Is there a way to call Syncronize in the worker to only wait for the Boss thread?
My code:
type
TWorker = class(TThread)
private
fResult : TResultRecord;
procedure SetOnSendResult(const Value: TNotifyEvent);
....
....
public
property OnSendResult: TNotifyEvent write SetOnSendResult;
property Result : TResultRecord read fResult;
....
end;
...
...
procedure TWorker.SendBossResults;
begin
if (Terminated = False) then
begin
Synchronize(SendResult);
end;
end;
procedure TWorker.SendResult;
begin
if (Terminated = false) and Assigned(FOnSendResult) then
begin
FOnSendResult(Self);
end;
end;
Then in my Boss thread I will do something like this
var
Worker : TWorker;
begin
Worker := TWorker.Create;
Worker.OnTerminate := OnWorkerThreadTerminate;
Worker.OnSendResult := ProcessWorkerResults;
So my boss then has a method called ProcessWorkerResults - this is what gets run on the Synchronize(SendResult); of the worker.
procedure TBoss.ProcessWorkerResults(Sender: TObject);
begin
if terminated = false then
begin
If TWorker(Sender).Result.HasRecord then
begin
fResults.Add(TWorker(Sender).Result.Items);
end;
end;
end;
Synchronize is specifically designed to execute code in the main thread; that's why it seems to lock everything up.
You can use several ways to communicate from the worker threads to the boss thread:
Add a callback to each worker thread,
and assign it from the boss thread
when it's created. It can pass back
whatever as parameters, along with a
thread ID or some other identifier.
Post a message from the worker thread
to the boss thread using
PostThreadMessage. The
disadvantage here is that the boss
thread has to have a window handle
(see Classes.AllocateHWnd in the
Delphi help and David Heffernan's comment below).
Use a good quality third-party
threading library. See
OmniThreadLibrary - it's free,
OS, and extremely well written.
My choice would be the third. Primoz has done all the hard work for you. :)
After your comment, here's something along the lines of my first suggestion. Note that this is untested, since writing the code for a TBoss and TWorker thread + a test app is a little long for the time I have right this minute... It should be enough to give you the gist, I hope.
type
TWorker = class(TThread)
private
fResult : TResultRecord;
fListIndex: Integer;
procedure SetOnSendResult(const Value: TNotifyEvent);
....
....
public
property OnSendResult: TNotifyEvent write SetOnSendResult;
property Result : TResultRecord read fResult;
property ListIndex: Integer read FListIndex write FListIndex;
....
end;
type
TBoss=class(TThread)
private
FWorkerList: TThreadList; // Create in TBoss.Create, free in TBoss.Free
...
end;
procedure TWorker.SendBossResults;
begin
if not Terminated then
SendResult;
end;
procedure TBoss.ProcessWorkerResults(Sender: TObject);
var
i: Integer;
begin
if not terminated then
begin
If TWorker(Sender).Result.HasRecord then
begin
FWorkerList.LockList;
try
i := TWorker(Sender).ListIndex;
// Update the appropriate record in the WorkerList
TResultRecord(FWorkerList[i]).Whatever...
finally
FWorkerList.UnlockList;
end;
end;
end;
end;
You could use a thread safe queue. In DelphiXE there is the TThreadedQueue. If you don't have DXE, try OmniThreadLibray - this library is very good for all threading issues.
As I mentioned new options in Delphi 2009 and higher, here is a link to an example for Producer / Consumer communication between threads, based on the new objct locks, in my blog:
Thread Synchronization with Guarded Blocks in Delphi
In a note regarding the deprecated methods TThread.Suspend and
TThread.Resume, The Embarcadero DocWiki for Delphi
recommends that “thread
synchronization techniques should be
based on SyncObjs.TEvent and
SyncObjs.TMutex.“ There is, however,
another synchronization class
available since Delphi 2009: TMonitor.
It uses the object lock which has been
introduced in this version ...
public properties of the TWorker class MUST have get and set methods, so you can use a Tcriticalsection to give the values of the properties. Otherwise, you´d be having thread-safe issues. Your example seems ok, but in the real world, with thousands of threads accessing to the same value would result in an read error. Use critical sections.. and you wouldn´t have to use any Synchronize. This way you avoid going to the message queues of windows and improve performance. Besides, if you use this code in a windows service app, (where windows messages aren´t allowed), this example wouldn´t work. The synchronize method doesn´t work unless there´s access to the windows message queue.
Solved!! (answer taken from the question)
The fixes made for this problem where two fold.
First remove the syncronization call in the TWorker SendBossResult method.
Second add a fProcessWorkerResult CritialSection to TBoss class. Create and Free this in create/destroy of the TBoss. In the ProcessWorkerResults method call fProcessWorkerResult.Enter and fProcessWorkerResult.leave around the code which needs to be safe from multiple worker results streaming in.
The above was the conclusion after Kens code and follow up comment. Many thanks kind sir, hats off to you!.