4 个版本
0.1.3 | 2024 年 3 月 5 日 |
---|---|
0.1.2 | 2024 年 2 月 14 日 |
0.1.1 | 2024 年 2 月 13 日 |
0.1.0 | 2024 年 2 月 13 日 |
#225 在 并发 中
每月 156 次下载
11KB
fork-map
一个用于在由 fork()
产生的子进程中运行操作的 Rust 库。拥抱恐惧的并发:担心你的工作任务会搞乱你的内存空间。
示例
use fork_map::fork_map;
pub fn do_with_fork(value: u64) -> u64 {
// Spawn a child process with a copy-on-write copy of memory
unsafe {
fork_map(|| {
// Do some obnoxious operation with `value`
// * Maybe it leaks memory
// * Maybe it uses static resources unsafely and
// prevents multi-threaded operation
// * Maybe you couldn't figure out how to
// send your data to a thread
Ok(value * 10)
}).unwrap()
}
// Execution continues after the child process has exited
}
动机
某些操作如果在它们自己的进程中运行效果最佳。无论它们是否施加单线程限制,在运行时消耗了无法言喻的资源,或者你只是想利用写时复制内存来消除启动时间,有时你真的只想 fork
和 map
。我对这个 crate 的主要用途是尝试嵌入 libClang,因为它不安全地使用静态内存,因为它假设它是单线程运行的,并且运行泄漏内存的操作。
实现和支持
fork_map
使用 libc::fork
编写,因此仅在支持 fork
的基于 *nix 的系统上才能正常工作(抱歉 Windows 用户!)。由于子进程继承父进程的内存空间(作为写时复制),对输入值或操作没有限制。结果值使用 serde_json
序列化,并通过一些非常 C 风格的不安全 io 代码通过 libc
文件句柄发送。父进程从文件中读取数据,并在子进程退出之前等待返回。
与 rayon
一起使用
通常期望您将想要与像 rayon
这样的东西一起使用这个 crate,因为对 fork_map
的调用会阻塞执行线程,直到子进程返回。结合使用,您可以有 rayon
协调一组工作线程池,每个线程池都生成并控制子进程,以最小的样板代码。我的许多用例最终看起来像这样
use fork_map::fork_map;
use rayon::prelude::*;
pub fn main() {
let my_big_list = [ /* ... */ ];
// Create a worker pool with rayon's into_par_iter
let results = my_big_list.into_par_iter().map(|item| {
// Have each worker spawn a child process for the
// operations we don't want polluting the parent's memory
unsafe {
fork_map(|| {
// Do your ugly operations here
Ok(item * 1234)
}).expect("fork_map succeeded")
}
}).collect::<Vec<_>>();
// Use results here
}
如果您有很多可以运行在子进程中的小型任务,您可以使用 rayon 的 chunks()
函数,并消除大量调用 fork()
的开销(这可能是显著的)
use fork_map::fork_map;
use rayon::prelude::*;
pub fn main() {
let my_big_list = [ /* ... */ ];
// Use rayon's chunks() to give each forked process more
// work to handle, if you have a lot of small tasks
let results = my_big_list
.into_par_iter()
.chunks(512)
.map(|items| {
// Now each child process does 512 items at once
unsafe {
fork_map(|| {
let mut results = vec![];
// Maybe this operation is only mildly heinous
// and we can do 512 of them before the child
// process needs to be restarted.
for item in items {
results.push(item * 1234);
}
Ok(results)
}).expect("fork_map succeeded")
}
})
.collect::<Vec<_>>();
}
安全性
由于fork()
的特性,这个函数非常不可靠,可能会违反Rust关于生命周期的大多数保证,因为所有内存都会在第二个进程中复制,即使它在你提供的闭包执行后调用exit(0)
。除了调用fork_map
的线程之外,其他线程都不会出现在新进程中,因此也违反了线程生命周期保证。请不要考虑使用这种异步执行器。
依赖项
~0.9–1.8MB
~39K SLoC