Monday, November 13, 2006

Cross thread calls in native C++, #1

As mentioned a few days ago, I intend to write down some of my ponderings and works on how to make synchronized calls across threads. The motivation for such mechanisms, is 1. to simplify inter-thread notifications, and 2. avoid cluttering classes and functions with more synchronization code than what's absolutely necessary. I assume that you, the reader, is at least vaguely familiar with threads, and all the pitfalls they introduce when common data is being processed. A classical example is the worker thread which fires off a callback function in a GUI class, to render some updated output. There are a bunch of different approaches, let alone patterns (e.g. Observer), to use in this case. I'll completely disregard the patterns, and focus on the actual data and notification.

Imagine the worker class Worker, and the GUI class SomeWindow. How they are associated makes little or no difference, what's important is that Worker is supposed to call a function, and/or update data in SomeWindow. The application has two threads. One "resides" in Worker, and the other in SomeWindow. Let's say that at a given point in time, the Worker object decides to make a notification to SomeWindow. How can this be done? I can sum up a few of the possible approaches, including major pros/cons.

  1. Worker accesses, and updates, a data member in SomeWindow.
    • Pros: It's quick.
    • Cons: It's dirty. More specifically, it breaks encapsulation. If this operation is done without some kind of interlocking (mutex / criticalsection / semaphore / etc.), the worker and window threads may both try to access the data member at once, and that is most certain to wreak havoc on our application. If we're lucky, it'll just cause an access violation. If SomeWindow exposes an object for interlocking, we break the encapsulation even further, unleashing ghosts such as deadlocks.
  2. Worker calls a function within SomeWindow, which updates a data member for us.
    • Pros: Granted the proper interlocking, it's relatively safe.
    • Cons: SomeWindow will be bloated with code for interlocking, in the worst possible case, one lock object per updatable piece of member data. It also arguably weakens the cohesion, by introduction of those very locks. Dealing with the complexities of threads, interlocking and synchronization in a verbose way is simply not very ideal in a GUI class.
  3. Worker sends a Window Message to SomeWindow, with the update data in a structure. SomeWindow deals with the message and somehow handles the data.
    • Pros: Relatively safe, if SendMessage is used.
    • Cons: Cohesion slightly weakened. Parameter translation and transport can become tiresome, as custom or generic structures are needed for each unique value lineup. The most prominent drawback of this approach is the link to window messages; it's not really practical for non-GUI scenarios.
  4. Worker calls a function within SomeWindow, which updates a data member for us, by use of a synchronized re-call.
    • Pros: Safe. Relatively effective. No bloat worth mentioning.
    • Cons: Cohesion slightly weakened. The code fundament is a wee bit more complex than it would be without the threads, but it's by no means incomprehensible, and the end-of-the-line code will be quite pleasant.

What this and future posts will dig into is option number four. I'll also present a templated framework I've been constructing, which allows us to create this kind of functionality with as miniscule a headache as possible. I'll leave it hanging here for now, though. Look back for episode two shortly.

0 kommentarer: