#子进程 #并行 #诅咒 #假设

fork-map

一个用于在由 fork() 产生的子进程中运行操作的 crate

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并发

Download history 29/week @ 2024-04-01

每月 156 次下载

MIT 许可证

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
}

动机

某些操作如果在它们自己的进程中运行效果最佳。无论它们是否施加单线程限制,在运行时消耗了无法言喻的资源,或者你只是想利用写时复制内存来消除启动时间,有时你真的只想 forkmap。我对这个 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