引言
科学计算程序往往需要运行数分钟甚至数小时。在这漫长的等待中,用户常常面临几个痛点:
- 程序是否在正常运行?进度如何?
- 发现参数设置有误,能否暂停调整而不是强制终止?
- 想查看当前中间结果,又不想打断计算流程?
process_monitor 是 utilsxx 库中的交互式进程监控组件。它以非侵入式的设计理念,让你只需继承一个基类、重写几个虚函数,就能为任何计算任务赋予暂停/继续/状态查看/安全退出的交互能力。本文将带你彻底掌握它的设计与使用。
一、核心设计:为什么需要 process_monitor?
1.1 传统方案的局限
| 方案 | 问题 |
|---|---|
Ctrl+C 强制终止 | 丢失中间结果,无法优雅收尾 |
信号处理(SIGINT) | 跨平台差异大,代码复杂 |
| 日志文件轮询 | 实时性差,无法交互控制 |
| 独立 GUI 监控 | 引入重量级依赖,不适合服务器环境 |
1.2 process_monitor 的设计哲学
- 非侵入式:你的计算逻辑几乎不用修改,只需在循环中插入一行
wait_for_keyboard() - 跨平台:Windows(
_kbhit/_getch)和 Unix(termios/select)统一封装 - 线程安全:键盘监控与计算逻辑分离,通过条件变量同步
- 可扩展:所有交互行为都是虚函数,可按需重写
1.3 状态机模型
RUNNING --[pause_key]--> PAUSED --[continue_key]--> RUNNING
| |
|--[quit_key] |--[quit_key]
v v
STOPPED <----------------- STOPPED
二、快速上手
2.1 最小示例
#include "process_monitor.h"
class MyCalculator : public utilsxx::process_monitor {
public:
void process() override {
while (!end_process()) {
// 你的计算逻辑...
do_calculation_step();
// 每秒钟检查一次键盘输入
wait_for_keyboard(1);
}
}
};
int main() {
MyCalculator calc;
calc.start_monitoring();
return 0;
}
运行后,你可以随时按下:
p— 暂停计算c— 继续计算s— 查看当前状态q— 安全退出(带确认)
三、架构详解
3.1 双线程架构
┌─────────────────┐ ┌─────────────────┐
│ 计算线程 │ │ 键盘监控线程 │
│ (process()) │◄────│ (keyboard_) │
│ │ cv │ │
│ wait_for_ │ │ get_keyboard_ │
│ keyboard() │ │ input() │
└─────────────────┘ └─────────────────┘
│ │
└────── 共享状态 ────────┘
end_process_, state_, iteration_count_
- 计算线程:执行你的
process()虚函数 - 键盘监控线程:以 100ms 间隔轮询键盘,解析命令
- 同步机制:
std::mutex+std::condition_variable实现暂停/唤醒
3.2 跨平台键盘输入
| 平台 | 技术 | 特点 |
|---|---|---|
| Windows | _kbhit() / _getch() | 原生支持,无需额外设置 |
| Linux/macOS | termios + select() | 关闭规范模式和回显,实现非阻塞读取 |
Unix 下的终端模式切换:
// 进入非阻塞模式(键盘监控时)
tio.c_lflag &= ~(ICANON | ECHO); // 关闭规范模式和回显
tio.c_cc[VMIN] = 0; // 最小字符数为0
tio.c_cc[VTIME] = 0; // 无超时
// 恢复规范模式(确认对话框时)
tio.c_lflag |= (ICANON | ECHO); // 开启规范模式和回显
注意:析构函数会自动恢复终端模式,防止程序异常退出后终端行为异常。
四、配置与定制
4.1 MonitorConfig 配置结构
struct MonitorConfig {
char pause_key = 'p'; // 暂停键
char quit_key = 'q'; // 退出键
char status_key = 's'; // 状态键
char continue_key = 'c'; // 继续键
bool show_status_on_pause = true; // 暂停时自动显示状态
int check_interval_ms = 100; // 键盘检查间隔(毫秒)
};
4.2 自定义按键
class MyCalculator : public utilsxx::process_monitor {
public:
MyCalculator() : process_monitor(MonitorConfig{
' ', // 空格键暂停
'q', // q 键退出
'i', // i 键显示信息
'r', // r 键继续(resume)
true, // 暂停时显示状态
50 // 50ms 检查间隔(更灵敏)
}) {}
void process() override { /* ... */ }
};
4.3 可重写的虚函数
| 虚函数 | 默认行为 | 重写场景 |
|---|---|---|
process() | 纯虚函数,必须实现 | 你的核心计算逻辑 |
show_status() | 显示状态、运行时间、迭代次数 | 显示自定义指标(如误差、收敛度) |
on_pause() | 显示暂停提示 | 保存检查点、释放资源 |
on_resume() | 显示恢复提示 | 重新加载检查点、恢复资源 |
五、完整示例:蒙特卡洛计算 π
以下是一个完整的交互式科学计算示例:
#include "process_monitor.h"
#include <cmath>
#include <random>
#include <iomanip>
#include <sstream>
using namespace utilsxx;
class MonteCarloPiCalculator : public process_monitor {
private:
int total_points_;
int points_in_circle_;
double current_pi_estimate_;
public:
MonteCarloPiCalculator() : process_monitor(MonitorConfig{
' ', // 空格键暂停
'q', // q键退出
'i', // i键显示信息
'c', // c键继续
true, // 暂停时显示状态
50 // 50ms检查间隔
}), total_points_(0), points_in_circle_(0), current_pi_estimate_(0.0) {}
void process() override {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(-1.0, 1.0);
std::cout << "Starting Monte Carlo π calculation..." << std::endl;
std::cout << "Press SPACE to pause, 'i' for info, 'q' to quit." << std::endl;
while (!end_process()) {
// 生成随机点
double x = dis(gen);
double y = dis(gen);
// 检查点是否在单位圆内
if (x*x + y*y <= 1.0) {
points_in_circle_++;
}
total_points_++;
// 计算π的估计值
current_pi_estimate_ = 4.0 * points_in_circle_ / total_points_;
// 每1000次迭代更新状态信息
if (total_points_ % 1000 == 0) {
std::stringstream ss;
ss << "π ≈ " << std::fixed << std::setprecision(8) << current_pi_estimate_
<< " (error: " << std::abs(current_pi_estimate_ - M_PI) << ")";
set_status_info(ss.str());
}
// 更新迭代计数
increment_iteration();
// 模拟计算延迟
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::cout << "\nFinal result after " << total_points_ << " iterations:" << std::endl;
std::cout << "π ≈ " << std::fixed << std::setprecision(10) << current_pi_estimate_ << std::endl;
}
// 重写状态显示:展示计算细节
void show_status() override {
process_monitor::show_status(); // 调用基类显示基本状态
std::cout << "Calculation Details:" << std::endl;
std::cout << " Total points: " << total_points_ << std::endl;
std::cout << " Points in circle: " << points_in_circle_ << std::endl;
std::cout << " Current π estimate: " << std::fixed << std::setprecision(8)
<< current_pi_estimate_ << std::endl;
std::cout << " Actual π value: " << M_PI << std::endl;
std::cout << " Accuracy: " << std::fixed << std::setprecision(2)
<< (1.0 - std::abs(current_pi_estimate_ - M_PI) / M_PI) * 100.0 << "%" << std::endl;
}
// 重写暂停回调
void on_pause() override {
process_monitor::on_pause();
std::cout << "\nCalculation paused at iteration " << get_iteration_count() << std::endl;
std::cout << "Current π estimate: " << current_pi_estimate_ << std::endl;
}
// 重写恢复回调
void on_resume() override {
process_monitor::on_resume();
std::cout << "\nResuming calculation from iteration " << get_iteration_count() << std::endl;
}
};
int main() {
MonteCarloPiCalculator calculator;
calculator.start_monitoring();
return 0;
}
运行效果:
Starting Monte Carlo π calculation...
Press SPACE to pause, 'i' for info, 'q' to quit.
=== Process Status ===
State: RUNNING
Runtime: 15 seconds
Iterations: 14283
Info: π ≈ 3.14159245 (error: 1.927e-07)
Commands: ' '=pause, 'c'=continue, 'i'=status, 'q'=quit
====================
Calculation Details:
Total points: 14283000
Points in circle: 11227491
Current π estimate: 3.14159245
Actual π value: 3.14159265
Accuracy: 99.999994%
六、API 详解
6.1 进程控制
| 方法 | 说明 |
|---|---|
start_monitoring() | 启动计算线程和键盘监控线程,阻塞直到计算结束 |
process() | 纯虚函数,实现你的核心计算逻辑 |
wait_for_keyboard(sec) | 在计算循环中调用,检查暂停/退出状态 |
end_process() | 检查是否应该结束循环 |
set_end_process(ask) | 设置结束标志,ask=true 时会要求确认 |
6.2 状态查询
| 方法 | 说明 |
|---|---|
get_state() | 获取当前状态(RUNNING/PAUSED/STOPPED) |
get_runtime_seconds() | 获取已运行时间(秒) |
get_iteration_count() | 获取迭代次数 |
show_status() | 显示当前状态(可重写) |
6.3 回调钩子
| 方法 | 触发时机 |
|---|---|
on_pause() | 按下暂停键后 |
on_resume() | 按下继续键后 |
show_status() | 按下状态键后 |
6.4 辅助方法
| 方法 | 说明 |
|---|---|
increment_iteration() | 迭代计数加一 |
set_status_info(str) | 设置状态信息字符串 |
七、高级技巧
7.1 在暂停时保存检查点
void on_pause() override {
process_monitor::on_pause();
// 保存当前计算状态到文件
std::ofstream checkpoint("checkpoint.dat");
checkpoint << iteration << " " << current_result << std::endl;
checkpoint.close();
std::cout << "Checkpoint saved." << std::endl;
}
void on_resume() override {
// 可选:从检查点恢复
process_monitor::on_resume();
}
7.2 动态调整计算参数
void process() override {
while (!end_process()) {
// 根据当前状态调整步长
if (get_state() == ProcessState::RUNNING) {
step_size = adaptive_step_size();
}
compute_step(step_size);
increment_iteration();
wait_for_keyboard(1);
}
}
7.3 多阶段任务监控
void process() override {
// 阶段1:预处理
std::cout << "Phase 1: Preprocessing..." << std::endl;
for (int i = 0; i < 100 && !end_process(); i++) {
preprocess_step(i);
increment_iteration();
wait_for_keyboard(1);
}
// 阶段2:主计算
std::cout << "Phase 2: Main computation..." << std::endl;
set_status_info("Entering main computation phase");
while (!end_process() && !converged()) {
compute_step();
increment_iteration();
wait_for_keyboard(1);
}
// 阶段3:后处理
std::cout << "Phase 3: Post-processing..." << std::endl;
// ...
}
7.4 与进度条结合
#include "process_monitor.h"
#include "progress_bar.hpp"
class MonitoredTask : public utilsxx::process_monitor {
utilsxx::progress_bar bar_;
public:
void process() override {
bar_.hide_cursor();
bar_.reset("Computing", 1000);
for (int i = 0; i < 1000 && !end_process(); i++) {
bar_.tick();
do_work();
increment_iteration();
wait_for_keyboard(1);
}
bar_.show_cursor();
}
};
八、注意事项
8.1 线程安全
end_process()、get_state()、get_iteration_count()使用std::atomic,线程安全set_status_info()内部有锁保护,可在计算线程安全调用- 不要在
process()中直接操作终端输入输出(交给键盘线程处理)
8.2 终端模式恢复
如果程序异常崩溃导致终端模式未恢复,可以执行:
reset
# 或
stty sane
8.3 重定向输出时的行为
当程序输出被重定向到文件时,键盘交互仍然有效(因为监控使用 std::cin 而非 std::cout):
./my_program > results.log # 计算结果写入文件,键盘交互仍在终端
九、API 速查表
类与枚举
| 类型 | 说明 |
|---|---|
ProcessState | RUNNING, PAUSED, STOPPED |
MonitorConfig | 配置结构体(按键映射、检查间隔) |
process_monitor | 监控基类 |
构造函数与生命周期
| 方法 | 说明 |
|---|---|
process_monitor(config) | 构造函数,传入配置 |
~process_monitor() | 析构时自动回收线程、恢复终端 |
start_monitoring() | 启动监控,阻塞直到结束 |
计算线程使用
| 方法 | 说明 |
|---|---|
process() | 纯虚函数,实现计算逻辑 |
wait_for_keyboard(sec) | 检查控制状态,支持暂停唤醒 |
end_process() | 判断是否应结束循环 |
increment_iteration() | 迭代计数加一 |
set_status_info(str) | 设置状态信息 |
状态查询
| 方法 | 说明 |
|---|---|
get_state() | 获取当前运行状态 |
get_runtime_seconds() | 获取运行时间 |
get_iteration_count() | 获取迭代次数 |
可重写回调
| 方法 | 说明 |
|---|---|
show_status() | 显示状态信息 |
on_pause() | 暂停时调用 |
on_resume() | 恢复时调用 |
结语
process_monitor 为 C++ 科学计算程序提供了一种轻量级、非侵入式、跨平台的交互监控方案。它不需要 GUI、不需要网络、不需要复杂配置——只需继承基类,你的程序就拥有了暂停、继续、状态查看和安全退出的能力。
对于长时间运行的数值模拟、数据处理和优化算法,这种"随时可控"的体验不仅是便利,更是生产力的保障。
项目地址:utilsxx