1

I am experimenting with the libevent library. I defined a few events and I do not create any threads in my code.

My question is, if a few events can access/modify the same shared struct, do I need a mutex to lock the critical section to avoid race condition? Or is libevent designed in a way that events can only be executed consecutively, never concurrently?

Notes:

  1. But of course the question is only about the case in which my events do not spawn new threads themselves
  2. My concern is mainly from my experience with JavaScript: JavaScript uses event loop design as well but we can still corrupt data even if there is only one thread. The reason is that JavaScript can, as I understand, "pause" the execution of one event and start a second event, then resume the paused event. If the pause happens right between lines that modify a shared object, it can corrupt my object.
  3. RTFM already but does not find the definite answer I am looking for
D.J. Elkind
  • 367
  • 2
  • 8
  • We want an [MRE](https://stackoverflow.com/help/minimal-reproducible-example). For C, we want a _single_ `.c` file [_with_ (e.g.) `#include `]. But, _no_ (e.g.) `#include "myprogram.h"`. For runtime errors, it should compile cleanly. We should be able to download it and build and run it on our local systems [if we so choose]. We need to see how _you_ are using the library before we can comment. – Craig Estey May 30 '23 at 02:08
  • 2
    You seem to assert that there will be only one thread, but then ask whether you have to worry about concurrent processing. If there is only one thread then there is no concurrency. Nor, more generally, any data races, regardless of actual concurrency. No scope for a problem that could be addressed by introducing a mutex. – John Bollinger May 30 '23 at 02:09
  • 2
    That does not necessarily mean that the processing for two different events couldn't interfere with each other, but if any such situation arose then a mutex would not solve it. – John Bollinger May 30 '23 at 02:14
  • @JohnBollinger I think this is not the case of JavaScript's event loop--we can still corrupt our data even if there is only one thread because JavaScript can, as I understand, "pause" the execution of one event and start another, then resume the paused event. If the pause happens right between lines that modify a shared object, it can corrupt my object. And thus this question, does libevent follow the same pattern? – D.J. Elkind May 30 '23 at 02:15
  • Did you read my *second* comment, @D.J.Elkind? – John Bollinger May 30 '23 at 02:21
  • @JohnBollinger I read it. In my case, a mutex with timeout can solve it I believe. (a mutex without a timeout may or may not solve it, depending on how libevent is implemented--if it can pause an event even during mutex wait, then I think a normal mutex would do too) – D.J. Elkind May 30 '23 at 02:22
  • @CraigEstey I believe this is not about an MRE as I want to know if something can NEVER happen. If I prepared an MRE, most likely what I am concerned will not happen, but the concern is exactly that--there might exist some MRE that I dont know as of now, which might have the behavior I describe. – D.J. Elkind May 30 '23 at 02:27
  • 2
    @D.J. Elkind: Please *STOP* worrying about some false equivalency between C and Javascript. Please consider "upvoting" and "accepting" takaki's excellent reply [below](https://stackoverflow.com/a/76361211/421195). If your "C" program isn't calling libevent on multiple threads, you shouldn't have anything to worry about. This is true even if your app has multiple threads ... but only one thread ever calls libevent. If you *ARE* calling libevent on multiple threads, you should use a mutex. – paulsm4 May 30 '23 at 02:40
  • 1
    We want to be sure that your code does not bork things. Any non-threadsafe API/library is thread safe if only _one_ thread calls it [and, in the case of signals, any relevant ones are _only_ delivered to that single thread]. Or, a mutex is added [with possible cache flush] with signal masking to the single thread. An RTFM: https://libevent.org/ 3rd paragraph: _Libevent can also be used for multi-threaded applications, either by isolating each event_base so that only a single thread accesses it, or by locked access to a single shared event_base._ – Craig Estey May 30 '23 at 02:43
  • @paulsm4 if you have an authoritative source, please let me know. I am afraid I can not *STOP* only because you requires me so. – D.J. Elkind May 30 '23 at 02:48
  • This question is perfectly sensible. I don't understand all the above ado. Just because your program is single threaded as far as not creating any threads doesn't mean that some event handling component it uses won't spin up a thread and enter the program's callbacks on multiple threads concurrently. – Kaz May 30 '23 at 03:19
  • @Kaz agree...if someone knows a source, just share an authoritative link saying "no, libevent never behaves this way" and case closed... No wonder some are now saying SO becomes toxic these days... – D.J. Elkind May 30 '23 at 04:09

2 Answers2

4

Libevent is designed to be single-threaded by default. It provides a mechanism for asynchronous event notification, which means it handles the events in a non-blocking, event-driven way. If you have multiple events that can potentially modify the same data structure and you are not creating any threads yourself, you generally don't need to use a mutex to avoid race conditions because these events will not be executed concurrently, but rather one after another.

However, it's important to be aware that libevent does support multi-threaded mode, where multiple threads can be created and events handled concurrently. If you were to use this mode, you would indeed need to ensure proper synchronization (like using mutexes) when accessing shared data to avoid race conditions.

To further clarify, in a single-threaded event-driven model like the default mode in libevent, there is a single event loop that processes events one at a time in the order they are triggered. While an event handler is running, no other event can be processed. This effectively prevents race conditions without the need for additional synchronization.

Remember that this holds true as long as your events don't spawn new threads themselves and you're using libevent in its default, single-threaded mode. If your application or the library is configured to use multiple threads, then you would need to ensure proper synchronization.

takaki
  • 81
  • 5
  • What you said is how I currently understand the design of libevent. But I am a bit concerned as this is not how JavaScript works--it is single-threaded and it uses an event loop. However, it is possible to "pause" an event (EventA) in the middle and process the second event (EventB), then we resume EventA. If EventA and EventB modify the same object, even if we use an event loop, race condition is still possible. Do you have any doc saying that this JavaScript-like behavior never happens in libevent? – D.J. Elkind May 30 '23 at 02:20
  • As far as documentation goes, while the libevent API doesn't explicitly state this, it's generally understood based on how libevent works. The sequential execution model is typical of many event-driven libraries in C and C++. Libevent's design is based on the reactor pattern, which is a synchronous event handling model where handlers are invoked directly and synchronously by the demultiplexer. – takaki May 30 '23 at 02:32
  • This is also what I currently think about--JS can pause an event because it has a virtual machine and thus it can do whatever it wants to my code. It appears to me that libevent might not have the ability to dynamically control how my event, as assembly code, is executed on a CPU--unless it can somehow inject a `sleep()` to it, which doesn't sound right. – D.J. Elkind May 30 '23 at 02:51
  • A single-threaded event loop is only safe if it borrows your application's one and only thread. If the component has its own thread servicing that event loop, and that thread can call into the application code, there could be races and deadlocks between that and the application's thread. – Kaz May 30 '23 at 03:23
0

My concern is mainly from my experience with JavaScript: JavaScript uses event loop design as well but we can still corrupt data even if there is only one thread. The reason is that JavaScript can, as I understand, "pause" the execution of one event and start a second event, then resume the paused event.

What you describe is atypical of event loops. They usually rely on a queue of events, processed one after another.

And what you describe is not really supportable in C's model of operation. The only way C defines for a thread's work to be preempted to perform different work in the same thread is if the thread receives a signal that causes a signal handler function to run. But signal handlers are not suitable for performing arbitrary processing, so although it's conceivable that libevent might use a signal handler to enqueue events, it is not plausible that it would call associated callbacks within the signal handler.

Rest at ease, then. Provided that you have only one thread, your event callbacks will not be preempted to run other event callbacks, and no two callbacks can run concurrently.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Can I interpret your answer as this: "in libevent, each callback is a blocking function call, libevent can do nothing between the time `after a callback is called and before such callback returns`."? – D.J. Elkind May 30 '23 at 03:02
  • @D.J.Elkind, The last sentence of this answer is its summary. That can be rephrased as "execution of your callbacks will not overlap, provided that you are using only one thread." There are undoubtedly various other libevent activities that are certain never to overlap with execution of your callbacks, but "libevent can do nothing" is too strong. Refer to this answer's comments about signal handling. – John Bollinger May 30 '23 at 03:10
  • got it, what you said provides great assurance to me. – D.J. Elkind May 30 '23 at 03:14