Wizards
Hmm, according to this, I’m a “wizard” at programming. Somehow methinks this dude overestimates the skill required to work with multithreaded apps. I think I’m pretty damned good, but I don’t think I’d call myself a wizard. (Not to mention that that’d be pretty presumptuous.)
I also disagree with several of the statements in those slides as to why events are easier to handle than threads. For starters, I’ll say that I do agree that locking isn’t easy. In more complex cases, it’s easy to forget a lock somewhere and get corrupted data, or to mis-design a situation where you can get deadlocks.
However, the statement “with threads, even the simplest application faces the full complexity” is a bit disingenuous. There are several tools available for communication between threads (such as locking queues) that allow a programmer to avoid nasty locking situations. Are they suitable for all situations? No, of course not. But sometimes, with a simpler multithreaded application, you can get away with ignoring mutexes, semaphores, condition variables, etc., depending on how the application works with its data – or rather, how the application needs to share its data between different threads. Sometimes all that is necessary is to have a single data structure that holds “aggregate” data provided by multiple threads, and in that case all you need is a single mutex to protect that data structure. Sure, there are more complicated examples, but that’s just my point: there are varying levels of complexity in multithreaded applications.
I’m also not seeing how the event-driven model is all that easy. It usually requires thinking about your program flow in a completely different manner than if you’re using threads. Event-driven apps require asynchronous callbacks, and often a complicated state machine.
A great example of this stems from my work on my mail watcher plugin for the Xfce panel. The original mail checker had a terrible flaw: all network I/O was done synchronously, so any network delays would cause the whole panel to become unresponsive. I considered both solutions to this problem:
-
Using an event-driven model, single-threaded, with non-blocking network sockets
-
Using a multithreaded design, one thread per connection, with blocking sockets
Ultimately, I chose the second option. This way, I could have this program flow on checking for new messages:
-
Convert the server’s hostname into an IP address.
-
Connect to the server.
-
Do the initial handshaking with the server and authenticate.
-
Check for new messages.
-
Log off from the server and disconnect.
With a multithreaded system, I could do all of these in succession, without worrying about blocking. Either each step could complete in the blink of an eye, or it could take 30 seconds for each step. It doesn’t matter.
With an event driven system, I’d have to have a state machine with a state for every single time I try to receive data from the network. That means one state each for steps #1 and #2, two to four states for #3, one or two states for each message folder in #4, and at least once state for #5.
I suppose that isn’t so terrible, but consider that because of how the C library works, there’s no way to make #1 non-blocking. Also, for secure SSL connections, I would probably have to use a more complex interface into the security library I was using to avoid blocking within the library.
So eventually I decided to use threads. In the end, I had a couple small locking problems, which were immediately apparent and not difficult to fix. I don’t recall encountering any deadlock situations.
I guess it just really depends on how your mind works best…