#同步 #sync # #异步 #countdownlatch

无需 std latches

一个向下计数器(CountDownLatch),可用于同步线程或协调任务

4 个版本

0.2.0 2023 年 12 月 31 日
0.1.3 2023 年 12 月 21 日
0.1.2 2023 年 12 月 19 日
0.1.1 2023 年 12 月 18 日
0.1.0 2023 年 12 月 18 日

248并发

每月 39 次下载

MIT/Apache

100KB
2K SLoC

Crates.io docs.rs build status codecov download license

锁是一个向下计数器,可用于同步线程或协调任务。计数器的值在创建时初始化。线程/任务可能会在锁上阻塞/暂停,直到计数器减到 0。

std::sync::Barrier 相比,它是一个一次性现象,即计数器达到 0 后将不会重置。相反,它具有有用的属性,即它不会通过调用 count_down()arrive() 使它们等待计数器达到 0。这也意味着它可以由参与线程/任务多次减少。

另请参阅 C++ 20 中的 std::latch,Java 中的 java.util.concurrent.CountDownLatch,以及 Concurrent::CountDownLatch

快速入门

默认使用 atomic-waitsync 实现

cargo add latches

使用 stdtask 实现

cargo add latches --no-default-features --features task --features std

futex 实现

cargo add latches --no-default-features --features futex

另请参阅 应该使用哪一个?

当未启用 std 功能时,它可以与 no_std 一起使用。

用法

等待完成

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();

实现

同步

sync 实现是线程的默认实现。

功能依赖

  • 添加 std 功能将使其使用 std::sync::Mutexstd::sync::Condvar 作为条件变量,它支持超时。
  • 如果禁用了 std,则添加 atomic-wait 功能将使其使用 atomic-wait 作为条件变量,它不支持超时。
  • 如果同时禁用了 stdatomic-wait,则会在编译时抛出错误。

默认功能中同时启用了 atomic-waitsync,以便于使用。因此,如果您想使用 syncstd 并且不想导入不必要的包 atomic-wait,请禁用默认功能。

Futex

futex 的实现类似于 C++20 中流行的实现 std::latch,与 sync 实现相比,提供了略微更好的性能。

它不支持等待时的超时。

功能依赖

  • 它依赖于功能 atomic-wait 和包 atomic-wait,并且不能被禁用。

任务

task 的实现通常用于协调异步任务。

如果在 no_std 中,它需要 extern crate alloc

功能依赖

  • 添加 std 功能将使其在唤醒器收集时使用 std::sync::Mutex 作为线程互斥锁。
  • 如果禁用了 std,则添加 atomic-wait 功能将使其在唤醒器收集时使用 atomic-wait 作为线程互斥锁。
  • 如果同时禁用了 stdatomic-wait,则它将在唤醒器收集时使用自旋锁作为线程互斥锁。

相似之处与不同之处

相似之处

  • 所有实现都基于原子操作,因此在不支持原子的平台上无法工作,即 #[cfg(target_has_atomic)]
  • 如果未启用 std 功能,则所有实现都可以在 no_std 上使用。
  • 除了等待之外的所有方法:不需要 async/await

不同之处

sync task futex
计数器类型 usize usize u32
互斥锁 stdatomic-wait stdatomic-wait 或自旋锁 无互斥锁*
等待 阻塞 未来 阻塞
超时 需要 std 需要异步定时器 不支持

* 无互斥锁并不意味着它不需要消除竞态条件,实际上它使用 Futex(即 atomic-wait)代替。

应该使用哪一个?

如果您的项目使用 async/await 任务,请使用 task 实现。您可以通过添加 stdatomic-wait 功能使其在 门控 场景中具有更高的并发友好性。如下所示:

cargo add latches --no-default-features --features task --feature atomic-wait

如果您的项目中并发量较小,请使用 futex 实现。如下所示:

cargo add latches --no-default-features --features futex

否则,请使用 sync 实现。它与 task 实现具有相同的计数器类型 usize,以及 std::sync::Barrier。添加 std 功能将使其支持超时。请注意,它应该与 stdatomic-wait 功能之一一起使用,否则将抛出编译错误。如下所示:

# Both `sync` and `atomic-wait` are enabled in default features
cargo add latches

或启用 std 功能以支持超时

cargo add latches --no-default-features --features sync --features std

在高并发情况下,futex 实现和 sync 实现之间没有明显的性能差距。

此外,如果您正在将 C++ 代码迁移到 Rust,使用 futex 实现可能是一种更加保守的方法,例如,类似内存使用、ABI 调用等。请注意,futex 实现没有未定义的行为,这与 C++ 中的 std::latch 不同。

性能

运行基准测试

使用带有 atomic-wait 的所有实现运行基准测试(futex 实现依赖于 atomic-wait

cargo bench --package benches

使用带有 stdsync 实现运行基准测试

cargo bench --package benches --no-default-features --features sync --features std

使用带有 atomic-waittask 实现运行基准测试

cargo bench --package benches --no-default-features --features task --features atomic-wait

或使用 std 和比较组运行基准测试

cargo bench --package benches --features std --features comparison

等等。

总体基准测试包括线程调度开销,而 Latch 的速度比线程调度快得多,因此可能存在时间抖动和较大的标准偏差。所有总体基准测试都将有后缀 -overall

异步比较组也是基于原子的,并依赖于特定的异步库,如 tokioasync-std

同步比较组使用 Mutex 状态而不是原子。

许可

Latches 根据 MIT 许可证或 Apache 许可证版本 2.0 发布,由您选择。

依赖项

~0–11MB
~58K SLoC