English · 简体中文
轻量级 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<BufferedIo>"]
SP["StreamingProcess<br/>Process<StreamingIo>"]
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
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
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
- C++20:
[[nodiscard]]、move-only、std::span、std::jthread、std::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/samplecd 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() 不死锁 |
适合 echo、git 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();适合编译日志、大体积输出、边跑边处理等场景。进程仍在运行时即可读到数据。
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(),误用会在编译期报错。
struct Config {
std::string executable;
std::vector<std::string> args;
};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 写端 |
| 方法 | 说明 |
|---|---|
communicate(input = {}) |
写 stdin(可选)、收齐 stdout/stderr 并返回 |
| 方法 | 说明 |
|---|---|
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 格式 |