Skip to content

dadaozhichen/Subprocess

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

subprocess

English · 简体中文

CI C++20 CMake License Platform Tests

轻量级 C++20 子进程库,用于在 Linux/POSIX 上启动外部程序、读写 stdin/stdout/stderr,并回收子进程。

通过编译期模板区分两种 I/O 模式:Buffered(一次性取输出)与 Streaming(边跑边读),避免在运行时混用不匹配的 API。

架构

graph TB
    subgraph api ["公开 API(include/subprocess/process.hpp)"]
        CFG["Config"]
        BP["BufferedProcess<br/>Process&lt;BufferedIo&gt;"]
        SP["StreamingProcess<br/>Process&lt;StreamingIo&gt;"]
        OUT["Output"]
    end

    subgraph facade ["门面层(src/process.cpp)"]
        FWD["模板特化转发"]
    end

    subgraph factory ["工厂(src/iprocess_impl.cpp)"]
        MK_B["make_buffered_process()"]
        MK_S["make_streaming_process()"]
    end

    subgraph platform ["平台实现"]
        POSIX_B["PosixBufferedProcess"]
        POSIX_S["PosixStreamingProcess"]
        WIN["WindowsProcess(占位)"]
    end

    subgraph detail ["内部模块(src/detail/)"]
        FD["Fd(RAII)"]
        PIPE["pipe_io"]
        SYS["posix/sys<br/>fork · exec · pipe"]
    end

    CFG --> BP
    CFG --> SP
    BP --> FWD
    SP --> FWD
    FWD --> MK_B
    FWD --> MK_S
    MK_B --> POSIX_B
    MK_B --> WIN
    MK_S --> POSIX_S
    MK_S --> WIN
    POSIX_B --> FD
    POSIX_B --> PIPE
    POSIX_B --> SYS
    POSIX_S --> FD
    POSIX_S --> PIPE
    POSIX_S --> SYS
    BP -.-> OUT
Loading

Buffered 数据流(构造时启动 drainer,持续抽干 pipe):

sequenceDiagram
    participant User
    participant BP as BufferedProcess
    participant Drainer as jthread drainer
    participant Child as 子进程

    User->>BP: 构造 Config
    BP->>Child: fork + exec
    BP->>Drainer: 启动 stdout/stderr drainer
  loop 后台读取
        Child-->>Drainer: pipe 输出
        Drainer-->>BP: 写入缓存
    end
    User->>BP: communicate(input?)
    BP-->>User: Output
    User->>BP: wait()
    BP-->>User: exit code
Loading

Streaming 数据流(由用户驱动读取):

sequenceDiagram
    participant User
    participant SP as StreamingProcess
    participant Child as 子进程

    User->>SP: 构造 Config
    SP->>Child: fork + exec
  loop 边跑边读
        User->>SP: read_stdout() / read_stderr()
        Child-->>SP: pipe 数据
        SP-->>User: chunk
    end
    User->>SP: wait()
    Note over SP: drain 剩余 pipe 数据
    SP-->>User: exit code
Loading

特性

  • C++20:[[nodiscard]]、move-only、std::spanstd::jthreadstd::string_view
  • RAII:析构时自动 wait(),fd 自动关闭
  • 防 pipe 死锁:Buffered 模式后台 drainer 线程持续读取 stdout/stderr
  • 双模式 API:编译期分离 BufferedProcess / StreamingProcess
  • 进程控制:wait()wait_for()terminate()(SIGTERM)、kill()(SIGKILL)、is_running()pid()
  • stdin 支持:write_stdin() / close_stdin(),Buffered 另有 communicate(input)
  • PIMPL:公开头文件不暴露平台实现细节

平台支持

平台 状态
Linux / POSIX 可用
Windows 占位实现(构造时抛异常)

构建

要求:CMake 3.20+、支持 C++20 的编译器(GCC 10+ / Clang 12+)。

cmake -B build -S .
cmake --build build -j

运行示例

./build/sample

测试

cd build
ctest --verbose --output-on-failure
#
cmake --build . --target check

部分测试依赖 /usr/bin/python3(大 I/O、死锁、Streaming 场景)。

测试 说明
ReadStdout Buffered communicate() 读取 stdout
ReadStderr Buffered communicate() 读取 stderr
Stdin Buffered communicate(input) 写入 stdin
LargeIO 512 KiB 双 pipe 大流量输出
Streaming 进程运行中边读 stdout
StreamingStdin Streaming 写入 stdin 并读回
BenchIO Buffered / Streaming 吞吐基准
DeadlockDemo 并发写满 stdout+stderr,communicate() 不死锁
DeadlockWaitBeforeRead wait()communicate() 不死锁

快速上手

Buffered:短命令、一次性取输出

适合 echogit status、已知输入的 communicate() 等场景。后台 drainer 持续抽干 pipe,调用顺序宽松(可先 wait()communicate())。

#include "subprocess/process.hpp"

subprocess::Config cfg{
    .executable = "/bin/echo",
    .args = {"hello"},
};

subprocess::BufferedProcess proc(cfg);
subprocess::Output out = proc.communicate();
int exit_code = proc.wait();

// out.stdout_data, out.stderr_data

带 stdin:

subprocess::BufferedProcess proc({.executable = "/bin/cat"});
subprocess::Output out = proc.communicate("piped input\n");
proc.wait();

Streaming:长输出、实时读取

适合编译日志、大体积输出、边跑边处理等场景。进程仍在运行时即可读到数据。

subprocess::StreamingProcess proc({
    .executable = "/usr/bin/python3",
    .args = {"-c", "print('early'); import time; time.sleep(1); print('late')"},
});

std::string collected;
while (true) {
    std::string chunk = proc.read_stdout();
    if (chunk.empty()) break;
    collected += chunk;
}
proc.wait();

带 stdin(写完输入后需 close_stdin(),子进程才能看到 EOF):

subprocess::StreamingProcess proc({.executable = "/bin/cat"});
proc.write_stdin("hello stdin\n");
proc.close_stdin();

std::string out;
while (true) {
    std::string chunk = proc.read_stdout();
    if (chunk.empty()) break;
    out += chunk;
}
proc.wait();

工厂函数

auto proc = subprocess::launch<subprocess::BufferedIo>(cfg);
// 等价于 subprocess::BufferedProcess proc(cfg);

两种模式如何选择

BufferedProcess StreamingProcess
读输出 communicate() 返回完整 Output read_stdout() / read_stderr()
写 stdin communicate(input)write_stdin() write_stdin() + close_stdin()
内存 全部输出缓存在 std::string 用户提供 buffer,按需处理
线程 2 个后台 drainer(stdout + stderr) 无 drainer,读 pipe 由用户驱动
死锁 内部 drainer 自动处理 运行中需及时读 pipe;wait() 会 drain 剩余数据
典型场景 短命令、脚本包装、测试工具 长日志、实时进度、大 I/O

两种类型在编译期分离:Buffered 没有 read_stdout(span),Streaming 没有 communicate(),误用会在编译期报错。

API 概览

Config

struct Config {
    std::string executable;
    std::vector<std::string> args;
};

Output(Buffered 专有)

struct Output {
    std::string stdout_data;
    std::string stderr_data;
};

公共接口(两种模式共有)

方法 说明
wait() 阻塞直到子进程结束,返回退出码
wait_for(timeout) 超时返回 std::nullopt
terminate() 发送 SIGTERM(POSIX)
kill() 发送 SIGKILL(POSIX)
is_running() 子进程是否仍在运行
pid() 子进程 PID
write_stdin(data) 写入 stdin
close_stdin() 关闭 stdin 写端

Buffered 专有

方法 说明
communicate(input = {}) 写 stdin(可选)、收齐 stdout/stderr 并返回

Streaming 专有

方法 说明
read_stdout(span) / read_stderr(span) 读入用户 buffer,返回字节数,0 表示 EOF
read_stdout(max_bytes) / read_stderr(max_bytes) 返回 std::string,空串表示 EOF,默认块大小 4096

集成到你的项目

add_subdirectory(path/to/subprocess)

add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE subprocess)
#include "subprocess/process.hpp"

性能测试

./build/test_bench_io
./build/test_bench_io --bytes 16777216 --chunk 4096 --rounds 3

对比 Buffered communicate()、Streaming 循环读、Streaming 交错读写等路径的吞吐与正确性。

项目结构

include/subprocess/process.hpp    # 公开 API
src/process.cpp                   # 模板特化转发
src/iprocess_impl.cpp             # 平台工厂(POSIX / Windows)
src/platform/posix_process.cpp    # POSIX 实现
src/platform/windows_process.cpp  # Windows 占位
src/detail/                       # 内部实现(fd、pipe_io、posix 封装)
example/main.cpp                  # 示例
tests/                            # 测试与基准
LICENSE                           # Apache License 2.0

限制与后续计划

当前版本不包含

  • 环境变量(env)、工作目录(cwd
  • stdout/stderr 重定向到文件
  • Shell 模式、管道串联(pipeline)
  • Windows 完整实现

发布与下载

GitHub Actions 会在推送版本标签时自动构建并发布预编译包:

git tag v0.1.0
git push origin v0.1.0

也可在 GitHub Actions → Release → Run workflow 手动触发发布。

每个 Release 包含:

资产 说明
subprocess-x.y.z-Linux.tar.gz Linux 二进制包(静态库 + 头文件 + CMake 配置)
subprocess-x.y.z-Linux.zip 同上,ZIP 格式
subprocess-x.y.z-Source.tar.gz 源码包
subprocess-x.y.z-Source.zip 源码包,ZIP 格式

许可证

Apache License 2.0

About

A modern C++ library for subprocess management

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors