async-event
An efficient async condition variable for lock-free algorithms, a.k.a. "eventcount".
Overview
Eventcount-like primitives are useful to make some operations on a lock-free structure blocking, for instance to transform bounded queues into bounded channels. Such a primitive allows an interested task to block until a predicate is satisfied by checking the predicate each time it receives a notification.
While functionally similar to the event_listener crate, this implementation is more opinionated and limited to the async
case. It strives to be more efficient, however, by limiting the amount of locking operations on the mutex-protected list of notifiers: the lock is typically taken only once for each time a waiter is blocked and once for notifying, thus reducing the need for synchronization operations. Finally, spurious wake-ups are only generated in very rare circumstances.
Note that if you only need to send notifications to a single task, you may use instead the Diatomic Waker crate for extra performance.
This library is an offshoot of Asynchronix, an ongoing effort at a high performance asynchronous computation framework for system simulation. It is also used in the Tachyonix MPSC channel.
Usage
Add this to your Cargo.toml
:
[dependencies]
async-event = "0.1.0"
event_listener
Differences with This Event
primitive is expected to be faster than that of the event_listener
crate in the general case. That being said, your mileage may vary depending on your particular application and you should probably benchmark both.
The API is more opinionated and designed to preventing potential misuse such as:
- Forgetting to check again the predicate after requesting a notification, i.e. after a call to
Event::listen()
in theevent_listener
crate.async-event
provides instead theEvent::wait_until
method which takes care of checking the predicate whenever necessary to prevent races. - Confusion between
notify
andnotify_additional
in theevent_listener
crate. Our experience and the API of other similar libraries suggest that the latter is almost always what the user needs, so thenotify*
methods in this crate actually behave likenotify_additional
in theevent_listener
crate. - Inadequate atomic synchronization of the predicate. The
notify*
andwait_until
methods always insert atomic fences to ensure proper synchronization: there is no equivalent tonotify_additional_relaxed
.
Examples
Send a non-zero value asynchronously
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use futures_executor::block_on;
use async_event::Event;
let value = Arc::new(AtomicUsize::new(0));
let event = Arc::new(Event::new());
// Set a non-zero value concurrently.
thread::spawn({
let value = value.clone();
let event = event.clone();
move || {
// A relaxed store is sufficient here: `Event::notify*` methods insert
// atomic fences to warrant adequate synchronization.
value.store(42, Ordering::Relaxed);
event.notify_one();
}
});
// Wait until the value is set.
block_on(async move {
let v = event
.wait_until(|| {
// A relaxed load is sufficient here: `Event::wait_until` inserts
// atomic fences to warrant adequate synchronization.
let v = value.load(Ordering::Relaxed);
if v != 0 { Some(v) } else { None }
})
.await;
assert_eq!(v, 42);
});
Single-slot MPMC channel for non-zero values
See implementation in the examples
directory.
Safety
This is a low-level primitive and as such its implementation relies on unsafe
. The test suite makes extensive use of Loom and MIRI to assess its correctness. As amazing as they are, however, Loom and MIRI cannot formally prove the absence of data races so soundness issues are possible.
License
This software is licensed under the Apache License, Version 2.0 or the MIT license, at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.