An efficient async condition variable for lock-free algorithms, a.k.a. "eventcount".
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.
Add this to your Cargo.toml
:
[dependencies]
async-event = "0.2.1"
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
.
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);
});
See implementation in the examples
directory.
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.
This software is licensed under the Apache License, Version 2.0 or the MIT license, at your option.
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.