为啥 Rust RwLock 在 fork 中表现异常?
Posted
技术标签:
【中文标题】为啥 Rust RwLock 在 fork 中表现异常?【英文标题】:Why does Rust RwLock behave unexpectedly with fork?为什么 Rust RwLock 在 fork 中表现异常? 【发布时间】:2020-09-10 13:50:07 【问题描述】:当我使用 RwLock 和分叉时,我看到了一些我无法解释的行为。基本上,子进程报告 RwLock 仍被获取,而父进程没有,即使它们都运行相同的代码路径。我的理解是子进程应该收到父进程内存空间的独立副本,包括锁,所以他们应该报告不同的结果是没有意义的。
预期的行为是孩子和父母都报告“互斥锁持有:假”。有趣的是,当使用 Mutex 而不是 RwLock 时,这可以正常工作。
Rust Playground link
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
【问题讨论】:
工作原理是这只是未定义的行为:“如果调用线程未持有读写锁 rwlock,则结果未定义。” pubs.opengroup.org/onlinepubs/9699919799/functions/… 看来即使fork出的进程有一个父级内存的副本,但是锁的状态里面肯定有线程id,导致它行为不端。 ^ 与这些同步抽象通常构建在操作系统原语 (std::sync
) 之上的事实相结合,这对我来说似乎是一个答案,因此可能会泄露实现细节,尤其是unsafe
.
【参考方案1】:
我假设您在这里运行的是 Linux 系统。 Rust 这样做是因为 glibc 这样做,而 Rust 的 RwLock
是基于 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=%d\n", (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 为 rwlocks 指定了单个解锁函数,而 glibc 存储当前线程 ID 以确定当前线程持有的锁是读锁还是写锁。如果当前线程 ID 等于存储的值,则该线程具有写锁,否则,它具有读锁。所以你实际上并没有解锁孩子的任何东西,但你可能已经破坏了锁中的阅读器计数器。
如 cmets 中所述,根据 POSIX,这是子进程中未定义的行为,因为线程解锁不是持有锁的线程。为了让它工作,Rust 必须像 Go 一样实现自己的同步原语,这通常是一个主要的可移植性噩梦。
【讨论】:
以上是关于为啥 Rust RwLock 在 fork 中表现异常?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在具有一级索引的 MultiIndex 列的 pandas DataFrame 中表现不同?
为啥 clojure 的地图在 println 中表现得那样?