It has become rather common to simply spawn a thread to handle a periodic event and just leave it at that. For example, let’s say that every N seconds, we need to log some statistics in order to make sure that everything is going smoothly. Well, what could possibly be simpler than just creating a thread that does that very thing in parallel?
Indeed, it would be weird to find any suitable option that would be simpler. However, when dealing with systems that require high performance, “simple” may not be the most optimal option. And in this essay, I intend to show an alternative that is better in every way, without being exceedingly complex.
Why threads are overkill for (most) periodic events
Conceptually, a thread is very simple. It’s nothing more than a way for the operating system to multiplex a task. However, once we start analyzing the practical consequences of adding threads, that’s where the problems begin. In particular, using dedicated threads for periodic events carry several drawbacks. For instance: What happens if we need to cancel such event? Is there a way to safely canceling a thread, without asynchronous interruption? We need to start thinking about ways to communicate with that thread that don’t involve the dreaded race conditions.
There are even more complex problems: What happens if, instead of canceling a periodic event, we want to reschedule it? That is, we want it to fire every M seconds instead. Adding to these list of problems is the fact that, even for modern systems, creating and maintaining a thread isn’t exactly cheap either. In any UNIX-like system, a thread has a stack, a set of registers, and a signal mask, at the bare minimum. And of course, it’s an independent entity that can be scheduled by the operating system. So, while it may look like a simple solution, when we take a look beneath the surface, we encounter immense complexity.
Event loops as an alternative
So, what can we use instead? Before coming up with a solution, we must keep in mind that whatever we propose shouldn’t be too impractical or cumbersome, lest we risk inconveniencing any programmer we want to woo.
As an alternative, I propose the use of event loops, specifically, Intraway’s very own implementation. In some of our products we have been using them extensively. The transition has been smooth, and there’s even been a performance increase as a result.
An event loop works by registering events, that is, external input that you may be interested in. When replacing threads, all you need to do is essentially tell the event loop that you want a particular callback to be called at a specific time, optionally adding that said event is periodic, which means it will continually fire as long as it’s active.
What is to be gained by switching to this model? It’s now safe to stop, restart and modify such an event at any time, and the event loop will adapt accordingly to such changes. Instead of having a multitude of threads, a single one will maintain a list of events and react to them as they happen. This means that adding or removing events becomes extremely easy, and is not dependent on the amount of memory our program has access to. Furthermore, an event loop can handle more types of events than simple timers. For example, they can notify the application once a file has become readable, or when a network socket has some data in it. This makes event loops very useful and scalable: Aside from managing your timed events, you can plug in any code you need to manage network connections, or file monitoring, and performance will be far greater than it would be had you used threads.
Of course, it should be noted that by making a bunch of periodic events be managed by a single loop, it now becomes possible for one of them to block the entire loop. If the periodic task involves something of very high latency, or that needs a long time to finish, then it’s best to be left in a separate thread. However, those kinds of tasks are rare, and so, and event loop will suffice for most cases.