| Crates.io | latches |
| lib.rs | latches |
| version | 0.2.0 |
| created_at | 2023-12-18 11:12:00.987037+00 |
| updated_at | 2023-12-31 09:06:22.151843+00 |
| description | A downward counter (CountDownLatch) which can be used to synchronize threads or coordinate tasks |
| homepage | |
| repository | https://github.com/mirromutth/latches |
| max_upload_size | |
| id | 1073279 |
| size | 99,128 |
A latch is a downward counter which can be used to synchronize threads or coordinate tasks. The value of the counter is initialized on creation. Threads/tasks may block/suspend on the latch until the counter is decremented to 0.
In contrast to std::sync::Barrier, it is a one-shot phenomenon, that mean the counter will not be reset after reaching 0. Instead, it has the useful property that it does not make them wait for the counter to reach 0 by calling count_down() or arrive(). This also means that it can be decremented by a participating thread/task more than once.
See also std::latch in C++ 20, java.util.concurrent.CountDownLatch in Java, Concurrent::CountDownLatch in concurrent-ruby.
The sync implementation with atomic-wait by default features:
cargo add latches
The task implementation with std:
cargo add latches --no-default-features --features task --features std
The futex implementation:
cargo add latches --no-default-features --features futex
See also Which One Should Be Used?
It can be used with no_std when the std feature is not enabled.
use std::{sync::Arc, thread};
// Naming rule: `latches::{implementation-name}::Latch`.
use latches::sync::Latch;
let latch = Arc::new(Latch::new(10));
for _ in 0..10 {
let latch = latch.clone();
thread::spawn(move || latch.count_down());
}
// Waits 10 threads complete their works.
// Requires `.await` if it is the `task` implementation.
latch.wait();
use std::{sync::Arc, thread};
// Naming rule: `latches::{implementation-name}::Latch`.
use latches::sync::Latch;
let gate = Arc::new(Latch::new(1));
for _ in 0..10 {
let gate = gate.clone();
thread::spawn(move || {
// Waits for the gate signal.
// Requires `.await` if it is the `task` implementation.
gate.wait();
// Do some work after gate.
});
}
// Allows 10 threads start their works.
gate.count_down();
The sync implementation is the default implementation of threads.
Feature dependencies:
std feature will make it use std::sync::Mutex and std::sync::Condvar as condition variables, it supports timeoutsstd is disabled, add atomic-wait feature will make it use atomic-wait as condition variables, it does not support timeoutsstd and atomic-wait are disabled, it will throw a compile errorBoth atomic-wait and sync are enabled in default features for easy-to-use. So if you want to use sync with std and don't want to import the unnecessary crate atomic-wait, please disable default features.
The futex implementation is similar to popular implementations of C++20 std::latch, which provides slightly better performance compared to the sync implementation.
It does not support timeouts for waiting.
Feature dependencies:
atomic-wait and crate atomic-wait, and cannot be disabledThe task implementation is typically used to coordinate asynchronous tasks.
It requires extern crate alloc if in no_std.
Feature dependencies:
std feature will make it use std::sync::Mutex as thread mutexes on waker collectionstd is disabled, add atomic-wait feature will make it use [atomic-wait][atomic-wait] as thread mutexes on waker collectionstd and atomic-wait are disabled, it will use spinlocks as thread mutexes on waker collectionSimilarities:
#[cfg(target_has_atomic)]no_std if the std feature does not be enabledasync/awaitDifferences:
sync |
task |
futex |
|
|---|---|---|---|
| counter type | usize |
usize |
u32 |
| mutexes | std or atomic-wait |
std, atomic-wait, or spinlock |
No mutex* |
| waits | Blocking | Futures | Blocking |
| timeouts | Requries std |
Requries an async timer | Not support |
* No mutex doesn't mean it doesn't need to eliminate race conditions, in fact it uses Futex (i.e. atomic-wait) instead
If your project is using async/await tasks, use the task implementation. Add it with std or atomic-wait feature might make it more concurrency-friendly for gate scenarios. Like following:
cargo add latches --no-default-features --features task --feature atomic-wait
If the amount of concurrency in your project is small, use the futex implementation. Like following:
cargo add latches --no-default-features --features futex
Otherwise, use the sync implementation. It has the same counter type usize as the task implementation and std::sync::Barrier. Add it with std feature will make it supports timeouts. Note that it should be used with one of the std or atomic-wait features, otherwise a compilation error will be thrown. Like following:
# Both `sync` and `atomic-wait` are enabled in default features
cargo add latches
Or enable std feature for timeouts support:
cargo add latches --no-default-features --features sync --features std
Under a large amount of concurrency, there is no obvious performance gap between the futex implementation and the sync implementation.
Additionally, if you are migrating C++ code to Rust, using a futex implementation may be an approach which makes more conservative, e.g. similar memory usage, ABI calls, etc. Note that the futex implementation has no undefined behavior, which is not like the std::latch in C++.
Run benchmarks for all implementations with atomic-wait (the futex implementation depends on atomic-wait):
cargo bench --package benches
Run benchmarks with the sync implementation with std:
cargo bench --package benches --no-default-features --features sync --features std
Run benchmarks with the task implementation with atomic-wait:
cargo bench --package benches --no-default-features --features task --features atomic-wait
Or run benchmarks with std and comparison group:
cargo bench --package benches --features std --features comparison
etc.
Overall benchmarks include thread scheduling overhead, and Latch is much faster than thread scheduling, so there may be timing jitter and large standard deviations. All overall benchmarks will have name postfix -overall.
The asynchronous comparison groups are also atomic-based and depend on specific async libraries such as tokio and async-std.
The synchronous comparison group uses Mutex state instead of atomic.
Latches is released under the terms of either the MIT License or the Apache License Version 2.0, at your option.