Monday, June 22, 2009

MyQuery 2.4 - Of multithreading, messages and Windows

Warning!. This is not much database related, except that I write this as I am developing my Database Query Tool MyQuery 2.4 right now.

While working with MyQuery 2.4, which should be ready for beta real soon now, I have gotten myself into an issue with Windows and Multithreading. Actually, this issue exists to some extent most message based systems I guess, assuming they also support multithreading.

If you have not developed with Win16/Win32 or some other message based GUI system, then let me fill you in on how Windows does this. This to a large extent dates back to when Windows wasn't truly multi-threading at all (such as Win 3.0, Win 3.11 etc.). Even in those old versions of Windows, you could switch from application to application, window to window anyway. And the application didn't seem to be blocked (I am ignoring any general performance issues with those old versions of Windows here).

How did that work? It worked as the applications weren't just running away and handling all keyboard and mouse input all on it's own, and wasn't blocking when no action was needed. Instead, an application has a "message loop" that handles messages. If a message is to be handled, then it is handled, and then control is handed back to Windows itself, until I have another message meant for yours truly. OK Fine, this is a cheap way of "multithreading" which is also reasonably lightweight and works quite well.

What is not so good is that we, at times, need to deal with blocking operations. As long as I do not return from handling a message, the application is blocked now. And this goes for ALL parts of the application, GUI included! No repaints, nothing, dead. And there are some blocking operations we need to deal with here.

And not only that. Let's say we have a database connection that is shared by several objects, active at the same time, in your application. In my case, I have a bunch on modeless dialogs that show database data that share one connection. Usually, you would think that just putting a mutex on that connection would be enough: One Window does it's thing:
  • Wait for the Mutex until I have it.
  • While I have the Mutex, do my database things.
  • Release the Mutex when the database thing is done.
Now, I can end up in the same situation as when I have multiple interlocking mutexes, I get a deadlock. But this time the situation is worse, as the waiters aren't from the same group (i.e. if I have several mutexes deadlocking, the Mutex system can potentially figure this out). The Windows messages are stuck, as my modeless dialog is stuck waiting for a mutex, and the other modeless dialog is stuck, as it is waiting for a message whilst holding the Mutex.

In Windows, a modeless Dialog by default use the applications main message loop. If I remember correctly MFC (which I do not like, and I am not alone in this it seems) does this by using a smarter than usual message loop. I could have done that in my case I guess, but I decided on a different approach:
  • Send an application defined message to do the database processing.
In the processing of this message:
  • Get the Mutex, but do not wait for it.
  • If I got, then do the database processing and release the Mutex and reset the flag that I am waiting for a Mutex.
  • If I didn't get it, set a flag that I am waiting to get it.
For any message that i am not processing (I could probably pick some useful idle message here also):
  • If the flag that I am waiting for the mutex, then send the User defined message to myself.
As we can see, I am avoiding deadlocks here by converting blocked operations to messages, making everything that is blocking into a polling process for the resource in question, using a message for each poll. One thing I cannot do here though, is to send any messages to myself whilst I am processing the database stuff, at least not any that can potentially block (other messages will nicely queue up of course).

Is this a good an accepted way of doing things? Maybe, at least it seems to work. I can have a script running in one window, and another window using the same database connection, all protected by a mutex, but none of the windows blocking. Next time around, I will probably figure out a way to massage the message loop so I can do this smarter, but for now, this works.

And this is example of how locking always comes back at you. In particular when multiple locking systems interact (like a GUI, a threading system and a database. Or a database that at one place locks the table, and at another place locks a row).

/Karlsson
Back to MyQuery. And I will not blog more about it until 2.4 is done. I promise...

3 comments:

Antony said...

The trouble is that most Windows programmers never deal with threads.
In the OS/2 world, worker threads were strongly recommended because it was perceived as "bad" if any application were to take longer than 20ms in the message handler.
OS/2 had good APIs for internal queues, message passing and memory management which made inter-thread and inter-process communication very fast, efficient and easy.
Ahh... nostalgia.

Karlsson said...

As this moght eventually coming in handy for someone else doing similar development:
I actually changed my way or working with this. It was just too error-prone, and a bit too limiting. Also, I fear it was using up a bit too much resources. And it didn't work well in all cases, say when I had to deal with am error condition.

The main issue was that I was just getting a load of messages when idle, asking me to retry the connection. Usuallay this worked OK, but when I had some error, and had to, say, pop up an error dialog, messages were clogging up.

Instead I do this:
- In the user-defined emssage handler, that does the blocking call, I wait a bit for the Mutex, not much, but some (technically, I don't have to for this to work, actually).
- If I don't get the mutex, I will Post the same message to myself (Posting a message is asynchronous, message wise, in Windows). This is actually much simpler, and so far works like a charm.
- If I do get the Mutex, I just handle it, and that's that.

Karlsson said...

And I still cannot spell. But you knew that :-)