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.
vBulletin Message