引言

科学计算程序往往需要运行数分钟甚至数小时。在这漫长的等待中,用户常常面临几个痛点:

  • 程序是否在正常运行?进度如何?
  • 发现参数设置有误,能否暂停调整而不是强制终止?
  • 想查看当前中间结果,又不想打断计算流程?

process_monitorutilsxx 库中的交互式进程监控组件。它以非侵入式的设计理念,让你只需继承一个基类、重写几个虚函数,就能为任何计算任务赋予暂停/继续/状态查看/安全退出的交互能力。本文将带你彻底掌握它的设计与使用。


一、核心设计:为什么需要 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/macOStermios + 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 速查表

类与枚举

类型说明
ProcessStateRUNNING, 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