3 个不稳定版本
新版本 0.1.1 | 2024年8月18日 |
---|---|
0.1.0 | 2024年8月10日 |
0.0.1-pre | 2024年7月20日 |
172 in Unix API
222 monthly downloads
Used in signals_receipts
29KB
228 lines
sem_safe
一个Rust风格的、直接的POSIX信号量接口,强制执行信号量的安全使用。
示例
use sem_safe::unnamed::Semaphore;
use std::{pin::Pin, thread};
static SEMAPHORE: Semaphore = Semaphore::new();
fn main() {
let sem = Pin::static_ref(&SEMAPHORE);
let sem = sem.init().unwrap();
thread::spawn(move || sem.post().unwrap());
sem.wait().unwrap();
}
动机
POSIX信号量,特别是sem_post
函数,对于异步信号处理程序唤醒阻塞的线程非常有用,因为sem_post
是异步信号安全的(相比之下,许多线程唤醒API,如通道,并不保证这一点)。信号处理程序仍然需要非常小心,确保它们所做的一切都是异步信号安全的(例如,仅使用原子类型将信号信息传递给其他线程),但sem_post
提供了唤醒另一个线程的关键能力(例如,在正常上下文中进一步处理传递的信号信息,而不受异步信号安全的极端限制)。 (sem_post
的少数替代方案之一是“self-pipe”技巧,其中从信号处理程序向管道写入,从另一端阻塞读取的线程,但这相当复杂(因为需要设置管道、关闭-on-exec、非阻塞写入等)。
信号处理并非唯一的使用场景。这个crate提供了一个C API的类似实现,可用于各种其他信号量使用场景。目前,仅支持“未命名”信号量的API,包括在多个进程间共享或仅在单个进程中私有的模式。对于“定时等待”和“命名”信号量的其他API可能在将来实现。
设计
根据Rust的方式安全地使用POSIX信号量面临的挑战,以及这个crate提供的解决方案包括
-
要在多个线程间共享信号量,类型必须是
Sync,这需要“内部可变性”。这个crate通过实现自己的抽象来覆盖
UnsafeCell<libc::sem_t>来达到这一点,这也使得这种类型的值可以作为全局
static
项(不是mut
)使用,这可能很方便;或者这种类型的值可以是有较短生命周期的局部变量,并且执行寿命安全。 -
sem_t
类型的值必须从未初始化开始,然后通过调用sem_init()
进行初始化,然后才能对sem_t
应用其他操作。这个crate有独立的Semaphore
和借用SemaphoreRef
类型,以强制执行只能对初始化值的引用执行操作,并且引用只能在拥有值并初始化后获得。 -
反初始化(
sem_destroy()
)仅在丢弃拥有的Semaphore
并且它被初始化时进行。如果有任何存在的SemaphoreRef
,则会阻止丢弃,从而防止在仍有潜在使用位置时销毁信号量。 -
不清楚在用
sem_init()
初始化后是否允许移动sem_t
值。OpenIndiana手册页说,“复制”(地址将不同于初始化的位置)将是未定义的,这可能意味着移动的值也可能如此。这个crate使用Pin
来强制初始化后不能移动值。 -
sem_init()
必须只对一个sem_t
执行一次。因为这个crate直接使用原子操作(因为这个crate是no_std
),所以即使有额外的调用,也许是从多个线程并发调用,也强制执行这一点。
可移植性
这个crate已经在(目前仅限于x86_64)上确认可以构建并通过其测试。
- BSD
- FreeBSD 14.0
- NetBSD 9.1
- Linux
- Alpine 3.18(使用musl)
- Debian 12
- NixOS 24.05
- Ubuntu 23.10
- Solaris
- OpenIndiana 2023.10
它可能在其他POSIX操作系统上也能工作。如果没有,添加对其他POSIX操作系统的支持应该很容易,但可能需要对这个crate的条件编译和/或链接进行修改。
macOS 不支持
不幸的是,macOS不提供未命名的信号量API(违反了现代POSIX版本要求提供该API),因此这个crate无法在macOS上工作。如果这个crate将来添加对命名信号量的支持,看起来它应该在macOS上工作,因为它确实提供了这一点。
依赖项
~43KB