Friday, October 5, 2007

Spot the deadlock

This is the final full day I'll be here in Kona, Hawaii for the ANSI/ISO C++ standards meeting and they're just going through some of the minor issues, or actually "bugs" in the standard, I've been playing with the thread pool class from my post about thread pools. I included a small project to demonstrate the concept. However, in working with it, I found a subtle deadlock condition. Can you spot it? I'll give you a hint: SendMessage. I think this highlights the complexity that can creep into even the seemingly simplest of multithreaded code. Of course I wasn't being particularly careful while writing the code as it was only a conceptual exercise.

One common thing you may want to do with thread pools is to dispatch several tasks to the pool, do some other work, and then wait for the result. Another twist is that you want to continue working in the UI and then at some point in the future, a method is called when the set of tasks are completed. I've been adding some code to the SyncObjs.pas unit, and specifically the THandleObject class. I've added a class function called WaitForMultiple(). This allows you to pass in an array of THandleObject class instances on which to wait. You can wait for one or all. This is just a wrapper around WaitForMultipleObjectsEx. In the thread pool class I've added a parameter to the QueueXXWorkItem methods allowing you to optionally pass in a TEvent object that will be signaled once the work item completes. Since you may want to dispatch many work items, all of which must be completed before continuing, you'd call THandleObject.WaitForMultiple() or a new class method on the TThreadPool class.

An interesting thing to note is that if the UI thread blocks waiting for the work items, it seems that the items are not dispatched into the worker threads at all and the application hangs. There is nothing in the MSDN documentation for QueueUserWorkItem. If I perform the wait on a separate wait thread, it seems to work OK. Any ideas? Are the tasks dispatched onto the worker threads via synchronization with the message queue? I would have figured that there was a control thread that waits for things to exist in the work item queue and then does the dispatching.

13 comments:

  1. Thanks a lot Allen for your posts from Kona. They were very, very instructive. Once again, it was proved, imho, that a community driven development is the path to follow. Now I remember a post in .non-tech newsgroup (with title “Top 3 requests” IIRC), in which someone asked for thread-safe VCL (ie. a very daunting task, imho). Nick responded “We’re very interested in that” – response which triggered an avalanche of posts from which the conclusion was that isn’t needed to have a thread-safe VCL but rather a easy, flexible way of sync/async signaling/messaging/passing data between threads and only the main thread to have the UI message loop, ie. no forms, panels, group boxes aso. in 2ndary threads, even it would be nice. IOW, a very “doable” task, even in Tiburon time frame. It was an impressive example of how community reacts, simplifies, and gives the right focus which is needed today for any IT company, imho.

    On the technical side of things, “where is the code?” (TM) Can you post the new source in CC?

    ReplyDelete
  2. ...while waiting for your code, some more things to note:

    - Another multithreading scenario it would be a ThreadedDataModule. Yes, I know that nowadays we can create & use a TDataModule in a thread, but what I mean is to have a TDataModule assigned to a thread in a Delphi manner, ie. Bind the UI elements (“data aware controls”) to the data sources from the thread, connect to database, fetch the data, call the methods of the threaded datasets and the UI will respond etc. All these things would happen at different moments in time, at programmer's will. (ie. something like

    TMainForm.OnButtonClick(Sender);
    begin
    with MyThreadedDataModule.SQLTable1 do
    begin
    SQL.Text:='SELECT * FROM T1 WHERE '+cMyNewFilter;
    Open; //here the UI will be updated acordingly
    end;

    end;
    - Do you are aware of very interesting things at
    http://www.bluebytesoftware.com/blog/CategoryView,category,Technology.aspx

    especially at

    http://www.bluebytesoftware.com/blog/2007/04/19/MichaelSuesssParallelProgrammingInterviews.aspx

    and, of course, at

    http://www.thinkingparallel.com/


    hth

    ReplyDelete
  3. Your code will eventually call QueueWorkItem(Sender, WorkerEvent, WT_EXECUTEDEFAULT) and the WT_EXECUTEDEFAULT flag tells QueueWorkItem and hence QueueUserWorkItem will execute PaintLines and hence PaintLine in a non IO thread.

    If too many non IO threads attempt to update the screen while an IO thread is updating a screen, a deadlock will occur.

    Besides, non IO threads are not supposed to update the screen.

    ReplyDelete
  4. Anyway, what is eventually painted on the screen comes from a data structure.

    So, any access to a data structure must be serialized, and the serialization of the data structure for the screen occurs only in an IO thread.

    If disorderly access to the screen's data structure occurs, what happens is usually data corruption. The OS might not react kindly to that.

    ReplyDelete
  5. Chee Wee,

    The deadlock is not in the PaintLines call. It is perfectly fine to paint to the screen from a background thread.

    Allen.

    ReplyDelete
  6. Okay...

    In that case, isn't locking the canvas unnecessary?

    ReplyDelete
  7. I don't see any SendMessage call in your code, directly or indirectly.

    Is the exact code at: http://cc.codegear.com/Download.aspx?ID=25023 what you're talking about?

    ReplyDelete
  8. Now that I've sit myself down, examining the code by running it, instead of calculating the issues through my head, I see where your SendMessage call is. It's in the code within FForm.ListBox1.Items.Add.

    If SendMessage blocks, your app will be in a deadlock, isn't it?

    ReplyDelete
  9. Chee Wee,

    That's it. I was trying to highlight that making a "thread-safe" VCL is not something easily done nor desirable in all cases.

    Allen.

    ReplyDelete
  10. Calling QueueIOWorkItem appears to resolve your issue. I've experimented several times, and found no deadlocks.

    QueueWorkItem, performed the same number of times, seems to run into deadlocks more often.

    These tests are performed while within the IDE and stepping through the program, and may not be conclusive though.

    ReplyDelete
  11. Chee Wee,

    So tell me *why* calling QueueIOWorkItem resolves it? I have my doubts that that actually resolves the problem.

    Allen.

    ReplyDelete
  12. My goodness! you've been busy Allen!

    I'll have to take a moment to catch up on all your posts since September.

    ReplyDelete
  13. Coincidentally I have recently written a threading library for our projects here. I have an inter-process and inter-thread waitable queue, message posting and synchronous waits (e.g. a synch. wait where ProcessMessages is called when in the main thread, or a thread's message queue is pumped while in a thread). I am not using the APC functions yet - was thinking of looking into that.

    ReplyDelete

Please keep your comments related to the post on which you are commenting. No spam, personal attacks, or general nastiness. I will be watching and will delete comments I find irrelevant, offensive and unnecessary.