program testsimplethread;
{
This a demo how to use the simple thread handling in FreePascal 3.3.1
or higher.
It consists of four threads:
- The main thread
- Two worker threads
- And a controller thread
This pattern is such that the main thread needs to keep track
of just one thread: the controller thread.
The controller thread performs the waitfors for all
worker threads. It only returns if all worker threads
are finished.
At program end, the main program just needs to check the waitfor
for the controller thread.
Since here all threads are started from the main thread,
checking and possibly waiting for the controller thread
guarantees that all threads have finished.
This resembles Windows WaitForMultipleObjects,
but in a cross-platform way.
Warning:
On Windows and Linux 64 bit platforms, the code below is always
thread safe, even without the synchronizer, but that may be not the
case for other platforms.
If you need to exchange other data between threads, you need to
synchronize it, unless you know it is also thread safe.
Here consoleIO, gettickcount64, sleep and reading start are all
thread safe for Windows and Linux 64 bit.
On 32 bit platforms this is not the case, so I use a
TMultiReadExclusiveWriteSynchronizer anyway to show best practise.
In this particular code that does not matter, though:
You could remove the synchonizer.
}
{$mode objfpc}
{$modeswitch anonymousfunctions}
uses {$ifdef unix}cthreads,{$endif}sysutils,classes;
var
Sync:IReadWriteSync; { this is a COM interface }
WorkerThread1,
WorkerThread2,
ControllerThread:TThread;
start:int64;
begin
{ Create a synchronizer, despite its long name very lightweight }
Sync:=TMultiReadExclusiveWriteSynchronizer.create;
{ measure starttime in ticks, the sync is not really necessary in this
case, because it is the only write and the threads are not started }
Sync.BeginWrite;
start := gettickcount64;
Sync.EndWrite;
writeln('Program start time saved');
{ thread with some work load }
WorkerThread1:=TThread.executeinthread(procedure
begin
writeln('Test the workerthread1');
sleep(2000);
Sync.BeginRead;
writeln('WorkerThread1 done', ' took:', gettickcount64 - start);
Sync.EndRead;
end);
{ thread with not much work load }
WorkerThread2:=TThread.executeinthread(procedure
begin
writeln('Test the workerthread2');
Sync.BeginRead;
writeln('WorkerThread2 done', ' took:', gettickcount64 - start);
Sync.EndRead;
end);
{ a controller thead that does the waitfor for all worker threads
if you do that in the main thread waitfor is blocking it, so
this is an easy way out. }
ControllerThread:=TThread.executeinthread(procedure
begin
writeln('Test the ControllerThread');
WorkerThread2.waitfor;
WorkerThread1.waitfor;
Sync.BeginRead;
writeln('ControllerThread done', ' took:', gettickcount64 - start);
Sync.EndRead;
end);
{ simulate main workload }
writeln('perform main workload');
sleep(10000);
{ wait for the controller thread to finish
but only if it is still running }
writeln('Main workload finished', ' took:', gettickcount64 - start);
if not ControllerThread.finished then ControllerThread.waitfor;
writeln('Main thread finished');
writeln;
writeln('The work load is more or less time defined so let''s analyze:');
writeln;
writeln('WorkerThread1 should cost about 2000 ticks');
writeln('WorkerThread2 should cost close to nothing');
writeln('ControllerThread does the waitfor''s, so should take the ticks from WorkerThread1 and WorkerThread2 combined');
writeln('plus some ticks caused by context switching or the system');
writeln;
writeln('The whole program should finish in about 10 seconds (the workload of the main thread)');
writeln('but we performed much more work!');
end.