You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过systemctl优雅停止Rust多线程守护进程确保任务完成

systemd 下 Rust 高负载服务优雅停机实现方案

核心逻辑是对接systemd标准的SIGTERM停止信号流程,收到停止指令后先完成所有在途任务、回收全部工作线程再退出进程,既保证核心数据不丢失,也能让systemd准确感知服务状态。

第一步:调整systemd服务配置

替换原来依赖轮询配置文件的触发方式,直接用systemd原生的信号机制下发停止指令,服务配置参考:

[Service]
Type=simple
ExecStart=/path/to/your/compiled/rust_binary
# 指定停止时发送SIGTERM信号
KillSignal=SIGTERM
# 根据业务单任务最大处理时长配置最长优雅停机窗口,比如配置300秒,超过这个时间程序还没退出systemd才会强制发SIGKILL
TimeoutStopSec=300

配置完成后执行systemctl daemon-reload重载配置即可生效。

第二步:Rust端实现信号处理+优雅停机逻辑

核心要做三个改造:

  • 用跨线程可见的原子布尔值替代普通变量作为停机标记,无锁开销完全适配高负载场景
  • 注册SIGTERM信号监听,收到信号时仅修改停机标记,不在信号处理逻辑里做重操作
  • 维护所有工作线程的句柄,触发停机后等待所有工作线程完成当前正在处理的任务(包括进行中的HTTP请求、核心数据计算/落盘),全部线程回收完成后进程再主动退出。

核心代码参考

如果不想写unsafe的信号处理逻辑,可以引入轻量无运行时依赖的signal-hook库处理信号,生产环境踩坑概率更低:

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use signal_hook::consts::SIGTERM;
use signal_hook::iterator::Signals;

fn main() {
    // 全局停机标记,跨线程共享
    let is_alive = Arc::new(AtomicBool::new(true));
    let signal_is_alive = is_alive.clone();

    // 单独启动线程监听SIGTERM信号
    let mut signals = Signals::new([SIGTERM]).expect("register signal handler failed");
    thread::spawn(move || {
        for _ in signals.forever() {
            // 收到systemctl stop发来的信号,仅修改停机标记,不做其他重操作
            signal_is_alive.store(false, Ordering::SeqCst);
        }
    });

    let mut worker_handles = Vec::new();
    // 这里替换成你的动态线程调度逻辑,动态增减线程时同步维护句柄列表即可
    for worker_id in 0..thread::available_parallelism().unwrap().get() {
        let worker_is_alive = is_alive.clone();
        let handle = thread::spawn(move || {
            loop {
                // 轮询检查停机标记
                if !worker_is_alive.load(Ordering::SeqCst) {
                    // 标记已触发停止,先处理完当前线程手里已经拿到的所有任务
                    // 包括未完成的HTTP请求收尾、核心数据提交/落盘等操作
                    finish_worker_pending_task(worker_id);
                    break;
                }
                // 正常执行业务逻辑
                run_business_logic(worker_id);
            }
        });
        worker_handles.push(handle);
    }

    // 主线程阻塞等待所有工作线程全部处理完任务退出
    for handle in worker_handles {
        handle.join().expect("worker thread exited with panic");
    }
    // 所有任务完成,进程正常退出,systemd会立刻感知到服务停止完成
}

如果不想引入第三方依赖,也可以用标准库+libc做信号注册,注意信号处理器里只能执行异步信号安全的操作,也就是只能修改原子变量,不能做内存分配、锁操作等其他逻辑:

use std::sync::atomic::{AtomicBool, Ordering};
use libc;

// 静态原子变量作为停机标记
static IS_ALIVE: AtomicBool = AtomicBool::new(true);

extern "C" fn sigterm_handler(_sig: i32) {
    // 仅修改原子标记,禁止加其他逻辑
    IS_ALIVE.store(false, Ordering::SeqCst);
}

fn main() {
    // 注册SIGTERM处理器
    unsafe {
        libc::signal(libc::SIGTERM, sigterm_handler as libc::sighandler_t);
    }
    // 剩下的工作线程调度、等待回收逻辑和上面的示例一致
}

关键注意事项

  • 动态增减线程的场景下,用线程安全的Vec(比如加Mutex的Vec)存所有活跃工作线程的JoinHandle,新启动线程时把句柄存入,线程正常退出时移除对应句柄,停机时等待列表里所有句柄join完成即可,不需要提前预估停机耗时。
  • 工作线程检查到停机标记后,不能直接丢任务退出,必须先把当前已经从队列里取到的任务处理完再break,避免核心数据丢失。
  • 所用的HTTP客户端建议配置合理的请求超时,超时时间要小于systemd配置的TimeoutStopSec,避免卡在僵死请求上被systemd强制杀进程。
  • 这套方案下,执行systemctl stop命令时会阻塞到进程完全退出才返回,命令返回就代表所有在途任务已经处理完成、进程正常停止,systemctl显示的服务状态和实际状态完全一致,不会出现之前轮询配置文件时的状态不同步问题。

内容的提问来源于stack exchange,提问作者Dykeiichi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 07:03:27