You will by now have noticed the boolean parameter being passed to the threads 'create' method. This is the 'createSuspended' flag. It tells the operating system the initial state of the thread once it has been created. It can either be running (createSuspended=false) or suspended (createSuspended=true).
Why is this relevant?
Well, consider this simple thread class.
Code:
interface
uses
classes;
type
TAVThread = class(TThread)
protected
fPInt : ^integer;
public
constructor create;
procedure execute;
end;
implementation
constructor TAVThread.create;
begin
inherited create(false);
sleep(100);
new(fPInt);
end;
destructor TAVThread.destroy;
begin
dispose(fPInt);
inherited;
end;
procedure TAVThread.execute;
begin
while not self.terminated do
fPInt^:=fPInt^+1;
end;
This is where 'createSuspended' comes in.
Code:
interface
uses
classes;
type
TNoAVThread = class(TThread)
protected
fPInt : ^integer;
public
constructor create;
procedure execute;
end;
implementation
constructor TNoAVThread.create;
begin
inherited create(true);
sleep(100);
new(fPInt);
self.resume;
end;
destructor TNoAVThread.destroy;
begin
dispose(fPInt);
inherited;
end;
procedure TNoAVThread.execute;
begin
while not self.terminated do
fPInt^:=fPInt^+1;
end;
Of course, you don't have to call 'resume' within the constructor. You may want to create a thread and then have it sit there idle until its needed. In that case, simply ommit the 'self.resume' from the constructor and call the 'resume' method when you want the thread to begin executing.
Code:
... myThread.create(true); ... ... // We need our thread to get busy myThread.resume; ... ... // And now we want it to sit idle myThread.suspend; ...
Although this example is somewhat artificial because its unlikely you'd put 'sleep' in the constructor, if the constructor takes too long, the effect is the same. The 'execute' method could find itself trying to use variables etc. that are not yet fully initialised. On a single core machine you can get quite a while before the newly created thread gets some CPU time (I have just tried it on my Athlon 800 and I got around 1 second before I experienced an AV - For the curious, I had the constructor perform 2mil repititions of the calculation i=((i*2)/3) ), but on a dual (or more) core machine, this time will be radically reduced as the OS can shuffle threads around the multiple cores of the machine.
The other option you have to get around this problem of threads running before they are initialised is to handle the initialisation and cleanup in the 'execute' method itself and create the thread in its running state (createSuspended=false).
Code:
procedure TMyThread.execute;
begin
// Initialise here
try
...
// Main execute code here
...
finally
// Cleanup here
end;
end;
You may be asking yourself why you would want to suspend a thread when the whole idea is to have the thread running in the background. The answer is simple, performance. This is of particular concern when the application is running on a single core machine.
Whilst a thread is active, the OS will allocate CPU time to it... even if the thread simply checks whether it has a job to do and then yields to the OS, that takes time... time which could probably be better spent doing other things (rendering for example). But, when you suspend the thread, the OS simply skips it. This saves the time it takes to perform two context switches and the time the thread uses in determining it has nothing to do before yielding to the OS as it waits for a job.
This polling method of checking whether we need to do anything and yielding to the OS if there isn't can (if you have a lot of threads) result in slowdown that could affect the performance of your game as the system is constantly switching threads to check for work. It can also lead to delays in the commencement of job processing. This is because of how sleep works. If you say sleep(10), your thread will suspend its execution for 10ms. If you say sleep(1000), your thread suspends its execution for 1sec. Sleep(10) will result in a lot of context changes that could slow down your game. Sleep(1000) on the other hand won't have so many context switches, but could mean that in the worst case, the job you want your thread to do is effectively put on hold for 1sec.
This is why you would suspend threads that don't have anything to do. If you have multiple cores, you can get away with leaving more threads in their running state, but this could result in poor performance on single cored machines as the OS allocates time to the threads you haven't suspended. So bear this in mind when designing a multithreaded game.
So lets look at how you might use 'suspend' and 'resume' to ensure your thread is only running when it has work to do. In this example, I've created a simple job processing thread.
Code:
procedure TMyThread.execute;
var
aJob : TMyThreadsJobDescription;
begin
repeat
if (self.workToDo) then
begin
aJob:=self.getNextJob;
// Process the job here
try
aJob.onJobCompleted(self,aJob);
except
try
aJob.free;
except
end;
end;
end
else
begin
if not self.terminated then
self.suspend;
end;
until (self.terminated);
end;
procedure TMyThread.stop;
begin
self.terminate;
if (self.suspended) then
self.resume;
end;
procedure TMyThread.addJob(aJob:TMyThreadsJobDescription);
begin
// Add 'aJob' to the job queue
if (self.suspended) then
self.resume;
end;
The other two methods handle killing the thread and adding jobs.
The 'terminate' method we would normally use for terminating the thread only sets the 'terminated' flag to true. It does not resume a suspended thread, so if you don't resume the thread yourself, you could be waiting an awfully long time for your thread to terminate. For this reason, I've created a new method 'stop'. This calls 'terminate' and then checks whether or not the thread is suspended... if it is, it calls 'resume'. 'addJob' performs a similar check when a new job is added to the job queue.
So, with this simple approach, we have a thread that will fire up, and then suspend itself when it has nothing to do (saving clock cycles), but when we add a job or kill it, we have very little delay since it gets straight to work when we resume it.


Menu
Categories
Content Tags







vBulletin Message