Chapter 5. Event-driven programming vs. multi-threaded programming

One of the tasks of event-driven programming is to avoid blocking situations, another is to schedule the processor activities. Another approach to achieve these goals is multi-threaded programming.

The fundamental difference between both approaches is that in the case of event-driven programming the application controls itself, while in the case of multi-threaded programming additional features of the operating system are applied. The latter seems to have major advantages, for example blocking is impossible at all (if one thread blocks, the other threads may continue running), and scheduling is one of the native tasks of an operating system.

This is not the whole truth. First of all, multi-threaded programming has the disadvantage that every line of the program must follow certain programming guidelines, especially shared storage must be protected by mutexes such that everything is "reentrant". This is not very simple. On the contrary, event-driven programs can be "plugged together" from a set of basic components, and you do not need to know how the components are programmed.

Scheduling: Multi-threaded programs sometimes lead to situations where there are many runnable threads. Despite the capabilities of the operating system, every modern hardware has the restriction that it performs badly if the code to execute is wide-spread over the whole memory. This is mainly caused by limited cache memory. Many operating systems are not well enough designed to efficiently get around this bottleneck.

Furthermore, I think that scheduling controlled by the application that knows best its own requirements cannot be worse than scheduling controlled by the operating system. (But this may be wrong in special situations.)

Avoid blocking: Of course, an event-driven program blocks if it gets into an endless loop. A multi-threaded application does not block in this case, but it wastes CPU time. It is normally not possible to kill single wild-running threads because most programs are not "cancellation-safe" (a very high requirement). In O'Caml, the latter is only possible for the bytecode thread emulation.

Of course, if you must combine some non-blocking I/O with time-consuming computations, the multi-threaded program will block "less" (it becomes only slower) than the event-driven program, which is unavailable for a period of time.

To come to an end, I think that there are many tasks where event-driven programs perform as well as multi-threaded programs, but where the first style has fewer requirements on the quality of the code.

5.1. Combining both styles

Since Equeue 1.2, it is possible to use Equeue in a multi-threaded environment. The fundamental Equeue module is reentrant, and the Unixqueue module even serializes the execution of functions if necessary, such that the same event system may be used from different threads.

One idea is to program a hybrid server in the following way: One thread does all network I/O (using event systems), and the other threads execute the operations the server provides. For example, consider a server doing remote procedures (as most servers do). Such a server receives requests, and every request is responded. When the server starts up, the networking thread begins to wait for requests. When a complete request has been received, a new thread is started performing the requested operation. The network thread continues immediately, normally doing other network I/O. When the operation is over, an artificial event is generated indicating this situation (see below on artificial events). The artificial event carries the result of the operation, and is added to the event system directly from the thread that executed the operation. This thread can now stop working. The network thread receives this artificial event like every other event, and can start sending the result over the network back to the client.

Artificial events are new in Equeue 1.2, too. The idea is to use O'Caml exceptions as dynamically extensible sum type. For example:

exception Result of result_type ;;
...
add_event esys (Extra (Result r))
The Extra event constructor can carry every exception value.

Caveat. The Extra events are not associated to any group. Every event handler will get them.