当我使用 RwLock 和 fork 时,我看到一些无法解释的行为。基本上,子进程将 RwLock 报告为仍已获取,而父进程则没有,即使它们都运行相同的代码路径。我的理解是,子进程应该接收父进程内存空间的独立副本,包括锁,因此它们报告不同的结果是没有意义的。
预期的行为是子级和父级都报告"互斥锁持有:假"。有趣的是,当使用互斥体而不是 RwLock 时,这可以按预期工作。
锈游乐场链接
use libc::fork;
use std::error::Error;
use std::sync::RwLock;
fn main() -> Result<(), Box<dyn Error>> {
let lock = RwLock::new(());
let guard = lock.write();
let res = unsafe { fork() };
drop(guard);
match res {
0 => {
let held = lock.try_write().is_err();
println!("CHILD mutex held: {}", held);
}
_child_pid => {
let held = lock.try_write().is_err();
println!("PARENT mutex held: {}", held);
}
}
Ok(())
}
输出:
PARENT mutex held: false
CHILD mutex held: true
我假设你在这里运行的是Linux系统。 Rust 这样做是因为 glibc 这样做了,而 RustRwLock
是基于 glibc 在使用 glibc 的 Linux 系统上的 pthreads 实现。
您可以使用等效的 C 程序确认此行为:
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
pthread_rwlock_wrlock(&lock);
pid_t pid = fork();
int res = pthread_rwlock_unlock(&lock);
int res2 = pthread_rwlock_trywrlock(&lock);
printf("%s unlock_errno=%d trywrlock_errno=%dn", (pid == 0) ? "child" : "parent", res, res2);
return 0;
}
其中打印以下内容:
parent unlock_errno=0 trywrlock_errno=0
child unlock_errno=0 trywrlock_errno=16
16 在我的系统上EBUSY
。
对于 glibc 发生这种情况的原因是 POSIX 为 rwlock 指定了单个解锁函数,并且 glibc 存储当前线程 ID 以确定当前线程持有的锁是读锁还是写锁。 如果当前线程 ID 等于存储的值,则线程具有写锁定,否则,线程具有读锁定。 因此,您实际上并没有解锁孩子中的任何内容,但您可能已经损坏了锁中的读取器计数器。
正如评论中提到的,根据POSIX,这是孩子中未定义的行为,因为线程解锁不是保持锁的线程。 为了做到这一点,Rust 必须像 Go 一样实现自己的同步原语,这通常是一个主要的可移植性噩梦。