I am writing a multithreaded desktop application.
I am unsure about the implications that multitreading has on the architecture. There is a lot of literature on architecture, but I know none that takes multithreading into account. There is a lot of literature on the low level stuff of multithreading (mutexes, semaphores, etc.) but I know none that describes how these concepts are embedded into an architecture.
Which literature do you recommend to fill this gap?
My application consist of
Presentation
that creates and manages dialogs using a GUI toolkit,Kernel
that knows all about the domain of the application,Controller
that knows the Kernel
and the Presentation
and moderates between these two.To be more precise, here is how files are opened:
Presentation
signals a FileOpenCommand
.ApplicationController
recieves this signal and
ApplicationKernel
to create a File
object,ApplicationPresentation
to create a FilePresentation
object,FileController
object, passing the File
and the FilePresentation
to the constructor.FileController
registers itself as an observer on its File
and FilePresentation
.Let's say File
provides a long running operation Init()
that should not block the user interface. There are two approaches that came to my mind:
File::Init()
returns an object that encapsulates a thread and can be used to register an observer that is notified about progress, errors, completion etc. This puts a lot of responsibility into the FileController
(who would be the observer), because it is now accessed from both the main thread as well as from the working thread.Controller
. File::Init()
would return nothing, but the ApplicationKernel
would signal creation, progress and errors of long running operations in the main thread. This would drag a lot of communication though the ApplicationKernel
, turning it into something like a god object.Which of these two is the common approach to multithreading in a desktop application (if any)? Which alternative approaches do you recommend?
I suggest that you consider using the actor model. It is a concurrency abstraction that hides a lot of the details associated with threads, locks, etc.
Edit
Some additional comments spurred by @CMR's comment...
Under an actor model, I imagine that the application would still be structured using the same components as suggested in the question: Presentation
, ApplicationController
, etc. The difference with the actor model is that the components (now actors) would not hold direct references to each other. Rather, they would hold channels to which they could post asynchronous, immutable, messages to one another.
The sequence of events in the "open a file" case would be essentially the same in the actor model, except that channels would be passed to the FileController
in step 2.3 instead of direct object references. Similarly, observer registration in occurs through channels.
So what's the difference? The main difference is that none of the code needs to be thread-aware. Threads are invisible to the application logic, being the concern of the actor framework. If one can follow the discipline of only passing immutable objects through the channels (which some actor frameworks enforce), then virtually all the difficult logic associated with thread synchronization disappears. Of course, one has to switch mindsets from a synchronous programming model to an asynchronous one -- not necessarily a trivial task. However, it is my opinion that the cost of that switch is outweighed by the benefit of not having to think about thread-safety (at least in systems of some complexity).
In UI programming in particular, asynchronous models make it much easier to give nice user feedback. For example, a UI element may kick off a long-running task, display a "working..." message, and then go to sleep. Some time later, a message arrives delivering the results of the long running task which the UI element then displays in place of the "working..." message. In similar fashion, tree views can be built incrementally as each tree node's data arrives in an incoming message.
You can think of an actor model as a generalization of the classic UI "event pump" approach -- except that every component (actor) is running its own event pump simultaneously instead of one pump dispatching to a bunch of components. Actor frameworks provide a way to run large or even huge numbers of such "simultaneous pumps" with very low overhead. In particular, a small number of threads (say, one per cpu) service a large number of actors.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With