• Recent Tutorials

  • Tripping The Class Fantastic: Multi-threading Part 1

    Sample Job Processor

    You can download a more complete job processor along with a simple demonstration here.

    This example code illustrates most of whats been covered and will provide you with a working example to experiment with. The sample is very simple... the jobs job is to simply wait for a period of time.

    As always, you can use this code in your own projects so long as I get a little credit. Its functional and appeared to work well, but it was written specifically for this article so it hasn't been battle tested.

    Conclusion

    Hopefully, with this first installment, the world of multithreaded programming is now a bit clearer. When I first encountered it I actually couldn't believe how easy it was... and also how easy it was to completely mess up through a lack of understaning with regards to resource protection etc.

    My first attempt was a business critical service for one of my employers... it worked great for the first few days... then it crashed. Another few days... *CRASH* Needless to say I didn't score any brownie points for that effort but since then, I've moved on a little. The browser based game I run has its processing service (the ticker). It uses multiple threads to get the best out of the dual CPU machine it runs on. Written as a Windows service, it has the service thread (equivalent to the main VCL thread) which spawns a management thread when it needs to get to work. This in turn spawns 4 different types of thread... one which prepares the database for the tick (one instance), then when that completes, the main workhorse thread (two instances) and finally a cleanup thread (one instance) and the score calculator (one instance). Why am I telling you this?

    Well for two reasons...

    Firstly, for a bit of moral support if you are struggling with a multithreaded application. My first attempt was a complete failure, but with a bit of perseverance, I've managed to improve my understanding of the subject quite substantially. So much so that now the only time my multithreaded server apps stop working is when the server is shutdown or rebooted. So, if you're having problems stick with it or ask for a bit of help.

    And secondly, to try and illustrate whats possible with multithreading. Once you get your head around the potential pitfalls, you can do great things with it. As an example, if our ticker starts to struggle with its workload (if we get lots of players for example), the main workhorse thread has been designed such that it could be farmed out to other servers. The tick preparation thread includes a balancing phase that spreads the workload evenly between a specified number of threads. So... add another dual core server, tell the balancer to spread across 4 threads and then add some software synchronization to manage the two threads on the extra server and I have myself a processing farm.

    Anyhow, this has turned out to be a much longer article than I planned so its time for me to go. I hope its made things clearer if you are new to multithreading, but just in case... if you have any questions or you spot an error drop me a mail on athena at outer hyphen reaches dot com and I'll do my best to help you out.

    The next installment on multi-threading will cover debugging, throttling and some suggestions regarding tuning for single/multi core machines.

    Thanks for reading.... until next time, take care and happy coding
    Comments 6 Comments
    1. chronozphere's Avatar
      chronozphere -
      Fantastic articles athena. I will definitely read 'em all.

      Multithreading still scares me a bit and the only way to overcome that is by rolling up the sleeves haha... I would like to use them in my engine, to separate rendering and audio from the rest of the code.

      I really need to write an article too btw. There aren't that many and new site does a good job at presenting them.
    1. WILL's Avatar
      WILL -
      Quote Originally Posted by chronozphere View Post
      I really need to write an article too btw. There aren't that many and new site does a good job at presenting them.
      Feel free! We need a lot more beginner articles. Either an introduction to a specific API or a completely non-API specific article about the basics of game programming.
    1. AthenaOfDelphi's Avatar
      AthenaOfDelphi -
      Thanks

      It's not quite as scary as it may seem at first... although I speak from the point of view of someone who nearly got fired because the first time they used multi-threading was in a business critical application which kept crashing because I messed up

      After that, no programming problem has ever seemed quite so scary
    1. code_glitch's Avatar
      code_glitch -
      nice stuff. i have written one slightly less popular article as it would seem but am working on another as will would tell you with the newbie-frindly project or something
    1. Dan's Avatar
      Dan -
      AthenaOfDelphi,
      Do you have any comments on suspend and resume procedures being deprecated starting from D2009. Since you are using these methods in your examples it may confuse some people because stuff normally gets deprecated when better ways of doing things become available.
    1. AthenaOfDelphi's Avatar
      AthenaOfDelphi -
      Dan, apologies for the delay... well, what can I say... I didn't realise they were deprecated, certainly my compiler doesn't complain about anything (I get the feeling from reading one or two articles it should complain).

      Whilst I understand the theories I've read, I can honestly say I've (a) never used the suspend/resume mechanism for synchronising threads and (b) never had any problems using suspend/resume. I generally use it for job processing type scenarios where the thread put's itself to sleep until it is needed. Replacing these calls with an eventing mechanisms (wait_for_single_object IIRC) shouldn't be too much of a problem. Something like this:-

      Code:
      unit classSuspendableThread;
      
      interface
      
      uses
        sysUtils, classes, windows;
      
      type
        TSuspendableThread = class(TThread)
        protected
          fWakeUpEvent        : THandle;
          fSleeping           : boolean;
      
          procedure goToSleep;
        public
          constructor create(createSuspended:boolean);
          destructor Destroy; override;
      
          procedure terminate;
      
          procedure wakeUp;
      
          property sleeping:boolean read fSleeping;
        end;
      
      implementation
      
      constructor TSuspendableThread.create(createSuspended:boolean);
      var
        temp    : string;
        nameBuf : PWideChar;
      begin
        inherited create(createSuspended);
      
        temp:='WUE-'+intToHex(self.ThreadId,8);
        nameBuf:=strAlloc(length(temp));
        strPCopy(nameBuf,temp);
        fWakeupEvent:=createEvent(nil,false,false,nameBuf);
        strDispose(nameBuf);
      end;
      
      destructor TSuspendableThread.destroy;
      begin
        closeHandle(fWakeupEvent);
      
        inherited;
      end;
      
      procedure TSuspendableThread.terminate;
      begin
        TThread(self).terminate;
      
        if (fSleeping) then
        begin
          wakeup;
        end;
      end;
      
      procedure TSuspendableThread.wakeUp;
      begin
        if (fSleeping) then
        begin
          try
            setEvent(fWakeupEvent);
          except
          end;
        end;
      end;
      
      procedure TSuspendableThread.goToSleep;
      begin
        fSleeping:=true;
        waitForSingleObject(fWakeupEvent,INFINITE);
        fSleeping:=false;
      end;
      
      end.
      This class works, call 'wakeUp' instead of resume (although resume, or it's replacement start would still be needed if you created it suspended). You will of course note that the 'gotoSleep' method which replaces suspend is now protected. Calling it from anywhere other than the execute method will hold the calling thread.

      This solution is far from perfect. The Windows API calls keep a count of suspends and resumes which this class does not. I suspect there are numerous ways of solving the puzzle and your solution will depend very much on usage.

      Do I think deprecating them is a good idea... it would be a cold day in hell if I said yes. They are legitimate mechanisms for controlling threads (and as I say, ones I've used myself on numerous occasions with no problems). So, I've just gone looking for an answer. I think this excerpt sums it up nicely (taken from this article by Peter Richie):-

      For time eternal there has been a way to suspend (and by association resume) another thread. It wasn't until .NET 2.0 that someone took leadership and conceded that suspending another thread is not a safe thing to do. .NET 2.0 deprecates System.Threading.Thread.Suspend() and Resume() (although, Resume() isn't dangerous by association...).

      Basically, Suspend() has no care that the thread may actually be managing one or more complex invariants that can't be changed atomically. Normally with complex invariants synchronization is used to ensure while one thread is modifying the individual components of an invariant another thread doesn't try to access it. This is important because the validity of an invariant is rarely atomic; meaning changing an invariant may take several steps and may be in an unknown or invalid state between the first and the last step (a date is a good example, setting the day and month is two steps; until both steps are complete the date might be invalid). Suspending a thread circumvents the synchronization primitives and cuts the thread off at the knees.
      Basically, calling suspend from outside a thread is bad... well all I can say is "there's a suprise". I've never done it and I wouldn't encourage you to do it for the reasons Peter gives.... the caller of suspend has no idea about the status of the thread. So, maybe a better solution would to have been to change visibility of the suspend method so that it was only accessible to the thread itself.

      Who knows, but I certainly don't agree with removing them as it's going to mean we have some changes to do at work when we finally port to an up to date Delphi version.