Using the event loop to simplify multi-threaded systems
Photo by Christian Wiediger on Unsplash

Using the event loop to simplify multi-threaded systems

Here's a quick hack which is probably 'old hat' for most developers, but I haven't seen it written up elsewhere.

Motivation

Life is a lot simpler if code can be kept single threaded, particularly if one is not seeking a performance benefit or there is only a single core. Sometimes however handling blocking functions forces the use of multiple threads. When developing with an application framework, code can be simplified by using the event loop to worker and main threads.

In practice

Here's a simple example. This class handles REST requests. The RestServer class calls the callback functions RestHandler::handleDel, RestHandler::handlePut, RestHandler::handlePost and RestHandler::handleGet when REST requests are received over the network. The callbacks are made in the context of a worker thread. If the requests were processed on the same threads on which they arrived the handling code would need to be made thread safe. Instead, the request data is posted using an asynchronous connection to the main thread. Further processing of the request can then be done on the main thread. The bulk of the handling code therefore need not be thread safe (it may need to be re-entrant, but that's easier to achieve).

This is using the Qt framework but I've used the same technique all the way back to my MFC days.

Show me some code!

This is code that I wrote while experimenting with a 3rd party C++ REST library.

No alt text provided for this image

In the constructor we register the data type that we're going to pass between threads. Qt already handles POD and its own library types but any custom types need to be registered. The next part of the code simply registers the callbacks for each of the REST methods and sets up the header responses. The callbacks that we register here will be called in the context of a worker thread.

The last line uses Qt's signal/slot mechanism to connect our signal 'messageReceived' to the slot 'handleMessage'. The final parameter forces the use of an asynchronous, queued connection so the signal emitted by a worker thread is received by the thread in which the instance of the RestHandler was created (i.e. the main thread).

No alt text provided for this image

Each of the callbacks simply emits a signal which contains the type of request (GET, PUT etc) and the request payload. These functions are the only part of our handler code that runs on the worker thread.

No alt text provided for this image

The handleMessage function calls specific handlers for each of the methods and these calls will be on the main thread.

Qt-specific

One of the implications of this methods is that we will have to copy any data that is passed between the threads. If the data to be copied is large then, of course, the performance hit needs to be considered. One solution would be to 'std::move' the values but since the copy is being done within the framework code we may not have control over how the copy is done. In the case of Qt, may of its library classes implement copy-on-write (COW), which provides thread-safe reference counting and so data is copied only if and when a function writes to it.

Summary

If concurrency is needed to handle asynchronous calls rather than for performance gain it may be beneficial to 'post' data to the main thread for processing and the event loop can provide a means for doing it.




To view or add a comment, sign in

More articles by Allan Jones

Explore content categories