I have been looking into event driven programming in C and the language doesn't seem to have support for it. This has led me to wonder about the nature of event driven programming. Is it correct to say that an event handler basically spawns a thread that waits for a response from I/O and sets a variable? Or is there something more complicated to the process.
Event driven architecture simply means, that the main loop AKA event loop does not understand much about the data a program processes. It understands only enough, that it can dispatch the right events for the data.
So for example, if event is triggered by data coming from a socket, all the event loop needs to know is, what callback to call, when data comes from that particular socket.
Typically the event loop is in some library, such as libevent. So application code consists of just setup code and event handlers which are called by the event loop in the library.
Since you mention threads: a classic simple event driven application is single threaded. Each event handler or callback just does its thing as fast as it can, when it gets called, and returns. Blocking or waiting or sleeping is a big no no, it will block all other events!
Threads are often used, when there is some processing which takes long time. If it were done in the main thread with event loop, the long operation would have to be chopped into small pieces, so other events could be handled too. So it's simpler to have a worker thread, which can do the processing without interfering with event processing, and then generate event for main event loop when done.
Other case of using threads is, when there is something which must block. It may for example be some other library, which only provides a blocking API. If that API was to be used from the main thread event loop, it would block other events. Solution is to use that library from another thread, which can block, and then generate event for the main event loop when there's something interesting for other parts of the application.
But in a sense, event driven architecture is sort of opposite of multi-threaded architecture:
- In straightforward multi-threaded architecture, each thread waits for one kind of thing to process (a wait condition or a blocking queue or a blocking socket read or whatever, possibly with timeout to periodically check something), then does the processing, possibly signals other threads, and goes back to waiting. One thread does one thing, and there's a thread for each different kind of thing to do.
- In event driven architecture there's just one main event loop (usually from a library) which waits for all kinds of events and calls handlers, all in the same thread. One thread does everything, and there's a non-blocking event handler for each different things to do. The good thing about this is, no synchronization is needed, because it's all in one thread, which avoids all-too-common multi-threading bugs, keeps code simpler and avoid synchronization overhead. Downside is not taking advantage of multiple CPU cores, and risk of blocking event loop if developer is careless.
Of course a complex application can have several event loops in different threads, and also threads which do just single thing, so these can be mixed.
An event loop library could be single-threaded if it uses some multiplexing facility to wait for several events, inputs (and outputs, e.g. ability and readiness to write some bytes on some pipe or socket).
On Linux and POSIX systems, you could use some multiplexing syscall like poll(2) or select(2). So you can have single-threaded event loop libraries, and (at least historically, e.g. in the previous century) several event-looping libraries (like libev, libevent, Glib from GTK) are -or have been- single threaded (so they don't spawn new threads).
Even recent GUI frameworks like Qt5 or GTK3/Gnome still require (on Linux) the graphical user code to be in the main thread.
You could also have some event loop, even in free-standing C programs like those driving microcontrollers. Then they would use some code (perhaps in assembly) to wait for events or interrupts, etc.