
1. 项目概述为什么需要程序自动重启在Windows平台上用C开发的后台服务、守护进程或者长时间运行的应用我们经常会遇到一个头疼的问题程序因为某些不可预知的异常比如内存泄漏累积、第三方库崩溃、外部资源耗尽而意外退出。对于需要7x24小时稳定运行的服务来说这种非计划性停机是不可接受的。手动去重启不仅效率低下在深夜或者无人值守时更是灾难。“程序自动重启”这个需求本质上是在构建一个简单的容错和自愈机制。它不关心程序内部为什么崩溃那是开发和测试阶段要解决的它只负责一件事当目标进程消失时能立刻感知并重新把它拉起来保证服务的连续性。这个机制通常由一个“看门狗”Watchdog程序来实现。有源码在手意味着我们不是去调用一个黑盒工具而是要深入Windows API和进程管理的细节自己动手打造一个可靠、可控的看门狗。这个项目适合所有需要在Windows环境下部署稳定C服务的开发者、运维人员。无论你写的是一个交易引擎、一个数据采集服务还是一个游戏服务器这套机制都能为你的系统增加一层坚实的保障。接下来我会结合我多年在Windows服务开发中踩过的坑从设计思路到代码实现完整拆解如何用C实现一个工业级的进程守护重启器。2. 核心设计思路与方案选型实现自动重启听起来简单但设计上却有几个关键岔路口需要抉择。不同的选择直接决定了守护进程的可靠性、资源占用和实现复杂度。2.1 监控方式的抉择轮询 vs 事件通知最直观的想法是写一个循环每隔几秒检查一下目标进程是否还在。这就是轮询Polling。用CreateToolhelp32Snapshot和Process32First/Next遍历进程列表查找目标进程的PID。这种方法实现简单但缺点明显有延迟取决于轮询间隔且CPU占用随着轮询频率增加而上升不够优雅。更高效的方式是采用事件通知。Windows提供了WaitForSingleObject或WaitForMultipleObjects函数它们可以等待一个内核对象比如进程句柄变为有信号状态。当一个进程结束时其对应的进程句柄会自动变为有信号状态。这意味着我们的看门狗程序可以“休眠”在等待函数上直到目标进程退出时才被唤醒实现零延迟感知和零CPU占用在等待期间。这显然是更优的方案。所以我们的核心方案定为通过CreateProcess创建目标进程并获取其进程句柄然后主线程调用WaitForSingleObject在这个句柄上等待。一旦等待返回就意味着进程结束立即执行重启逻辑。2.2 重启策略的细化立即重启与延时重启不是所有崩溃都适合立刻重启。考虑一个场景程序因为访问一个暂时不可用的网络资源而崩溃如果立即重启它很可能再次尝试访问并再次崩溃形成“崩溃-重启-再崩溃”的死循环短时间内可能产生成千上万个进程实例耗尽系统资源。因此一个健壮的看门狗需要包含重启策略。最基本的策略是“指数退避”Exponential Backoff。即每次重启后如果进程再次很快退出则下一次重启的等待时间逐渐延长例如1秒2秒4秒8秒…直到达到一个上限如1小时。这给了系统或外部依赖一个恢复的时间窗口。同时还需要设置一个最大重启次数超过后则停止重启并记录错误需要人工干预。2.3 进程树管理与资源清理这是新手最容易忽略也最容易出问题的地方。当我们用CreateProcess创建进程时如果目标进程又创建了子进程比如调用系统命令、启动其他程序简单的监控父进程可能不够。父进程退出时子进程可能变成“孤儿进程”继续运行占用资源。更复杂的是我们需要确保在重启前目标进程及其所有子进程都被彻底清理。否则残留的进程可能会占用端口、文件锁等资源导致新进程无法启动。这涉及到遍历进程树并强制结束进程的操作需要使用TerminateProcess但需谨慎因为这是强制性的可能导致数据丢失。在我们的设计中将采用一种折中但实用的方案监控主进程。并在重启前尝试友好结束发送关闭消息主进程如果超时则强制终止该进程及其直接创建的子进程通过指定CREATE_BREAKAWAY_FROM_JOB标志的变通方式管理或使用Job对象但为简化初版我们先处理主进程。2.4 守护进程自身的可靠性“谁来看守看守者”这是一个哲学问题也是工程问题。如果看门狗程序自己崩溃了那一切都白搭。提升守护进程自身可靠性的方法包括代码精简健壮守护进程的逻辑应尽可能简单只做进程管理和重启避免复杂的业务逻辑。作为Windows服务安装将看门狗程序注册为Windows服务可以利用服务管理器的自动重启机制服务恢复选项来守护看门狗本身。这是最推荐的生产环境方案。双进程互保启动两个看门狗进程互相监控。但这增加了复杂性通常用于要求极高的场景。基于以上分析我们第一版的实现目标定为一个控制台模式的看门狗程序使用事件通知监控实现带指数退避的重启策略并妥善处理进程资源清理。后续可以很容易地将其封装为Windows服务。3. 核心实现细节与Windows API解析有了设计蓝图我们开始深入代码层面。这里会用到几个关键的Windows API理解它们的细节和陷阱至关重要。3.1 进程创建与句柄获取创建进程使用CreateProcess函数。它的参数很多但对我们最重要的有以下几个BOOL CreateProcessW( LPCWSTR lpApplicationName, // 可执行文件路径 LPWSTR lpCommandLine, // 命令行参数 LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, // 子进程是否继承句柄 DWORD dwCreationFlags, // 创建标志关键 LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, // 启动信息 LPPROCESS_INFORMATION lpProcessInformation // 【输出】进程和线程信息 );lpCommandLine这个参数需要可写的字符串。一个常见的坑是直接传入字符串常量这在某些编译器下会导致访问冲突。安全的做法是传入一个可写的字符数组。dwCreationFlags这里我们至少需要CREATE_NEW_CONSOLE或CREATE_NO_WINDOW。如果目标程序是控制台程序用前者如果是GUI或后台程序用后者CREATE_NO_WINDOW可以避免弹出黑框。更重要的一个标志是CREATE_BREAKAWAY_FROM_JOB但涉及Job对象我们稍后讨论。lpProcessInformation这是输出参数其中hProcess成员就是我们监控目标进程所需的句柄。务必在程序最后用CloseHandle关闭这个句柄和hThread句柄否则会造成句柄泄漏。一个健壮的创建流程如下STARTUPINFOW si { sizeof(si) }; PROCESS_INFORMATION pi { 0 }; wchar_t cmdLine[MAX_PATH] L\C:\\MyApp\\app.exe\ --arg1 value1; if (CreateProcessW(NULL, // 可执行文件路径已在cmdLine中 cmdLine, NULL, NULL, FALSE, CREATE_NO_WINDOW, // 或 CREATE_NEW_CONSOLE NULL, NULL, si, pi)) { // 成功pi.hProcess 即为我们需要的句柄 // ... 后续监控逻辑 // 注意不需要的线程句柄立即关闭 CloseHandle(pi.hThread); } else { DWORD err GetLastError(); // 处理创建失败例如记录日志 }3.2 进程等待与超时控制获取到进程句柄pi.hProcess后我们使用WaitForSingleObject进行等待。DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);hHandle: 要等待的句柄即pi.hProcess。dwMilliseconds: 超时时间单位毫秒。INFINITE表示无限等待直到进程退出。这里有一个非常重要的技巧如果我们单纯地WaitForSingleObject(pi.hProcess, INFINITE)那么看门狗线程将完全阻塞无法在等待期间做其他事情比如响应外部停止命令、记录心跳日志等。因此更佳实践是使用WaitForSingleObject带一个较小的超时时间例如1000毫秒在循环中检查。这样每次超时返回WAIT_TIMEOUT时我们可以检查一个全局标志位判断是否收到了退出信号。bool g_stopRequested false; HANDLE hTargetProcess pi.hProcess; while (!g_stopRequested) { DWORD waitResult WaitForSingleObject(hTargetProcess, 1000); // 等待1秒 if (waitResult WAIT_OBJECT_0) { // 目标进程已退出 std::cout Target process exited. std::endl; break; // 跳出循环执行重启逻辑 } else if (waitResult WAIT_TIMEOUT) { // 超时进程还在运行继续循环 // 这里可以插入一些周期性任务比如记录日志“进程还活着” continue; } else { // 等待失败可能是句柄无效 DWORD err GetLastError(); std::cerr Wait failed: err std::endl; break; } }3.3 进程终止与资源清理当需要主动重启或者看门狗自己要退出时我们需要先终止目标进程。直接调用TerminateProcess是最暴力的方式它相当于在系统层面“杀死”进程进程没有机会执行清理代码如保存文件、释放网络连接。BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode);最佳实践是尝试“友好终止”如果目标程序是我们自己开发的可以定义一种进程间通信方式如发送特定的Windows消息WM_CLOSE或向控制台发送CTRLC事件对于GUI程序可以找到其主窗口并发送关闭消息通知其自行退出。发送友好终止信号后等待一段时间例如30秒。如果超时后进程仍在再使用TerminateProcess强制结束。对于控制台程序发送CTRLC事件相对复杂需要用到GenerateConsoleCtrlEvent函数并且要求看门狗进程和被监控进程在同一个控制台组中这通常意味着需要特殊的创建标志或者使用Job对象。鉴于其复杂性很多生产环境中的看门狗在权衡后会直接采用“强制终止外部状态恢复”的策略。即假设进程是随时可以暴力杀死并重启的任何需要持久化的状态都应该存储在进程外部如数据库、文件进程启动时从外部恢复状态。在我们的示例中为了聚焦核心监控重启逻辑将采用“强制终止”的方式。但在实际项目中你需要根据目标进程的特性来决定终止策略。清理的关键步骤调用TerminateProcess(hTargetProcess, 1)。调用WaitForSingleObject(hTargetProcess, 5000)等待进程对象确实变为有信号状态确保系统已完成清理。调用CloseHandle(hTargetProcess)关闭句柄。3.4 指数退避重启算法的实现这是提升稳定性的核心逻辑。我们需要维护几个状态变量restartCount: 当前连续重启次数。baseDelay: 基础延迟时间毫秒例如1000。maxDelay: 最大延迟时间毫秒例如36000001小时。backoffFactor: 退避因子通常为2。算法如下进程退出进入重启逻辑。计算本次重启前的等待时间delay min(baseDelay * (backoffFactor ^ restartCount), maxDelay)。休眠delay毫秒使用Sleep函数。尝试重启进程。如果重启成功并且新进程稳定运行超过一个“成功阈值”时间例如60秒则重置restartCount为0。否则restartCount加1。如果restartCount超过最大允许重启次数例如10次则停止重启记录严重错误。这里“稳定运行”的判断可以通过在新进程启动后等待一个较短时间如成功阈值然后检查进程是否仍然存在来实现。这可以防止程序启动瞬间就崩溃导致的快速退避失效。4. 完整代码实现与分步解析下面我将呈现一个完整的、带有注释的看门狗程序示例。这个示例包含了上述的核心设计事件等待、强制终止、指数退避重启和基本的日志输出。#include windows.h #include iostream #include string #include chrono #include thread #include cmath // 配置结构体 struct WatchdogConfig { std::wstring targetPath; // 目标程序路径 std::wstring arguments; // 命令行参数 unsigned int maxRestarts; // 最大重启次数 unsigned int baseDelayMs; // 基础延迟(毫秒) unsigned int maxDelayMs; // 最大延迟(毫秒) unsigned int stableThresholdMs; // 稳定运行阈值(毫秒) }; class ProcessWatchdog { private: WatchdogConfig config; volatile bool stopRequested; HANDLE hProcess; unsigned int restartCount; unsigned int consecutiveStableTime; void Log(const std::string message) { auto now std::chrono::system_clock::now(); auto time std::chrono::system_clock::to_time_t(now); char timeStr[100]; ctime_s(timeStr, sizeof(timeStr), time); timeStr[strlen(timeStr) - 1] \0; // 去掉换行符 std::cout [ timeStr ] message std::endl; } // 计算下一次重启的等待时间 unsigned int CalculateBackoffDelay() { unsigned int delay config.baseDelayMs * static_castunsigned int(std::pow(2, restartCount)); return (delay config.maxDelayMs) ? config.maxDelayMs : delay; } // 启动目标进程 bool StartTargetProcess() { Log(Attempting to start target process...); std::wstring fullCmdLine config.targetPath; if (!config.arguments.empty()) { fullCmdLine L config.arguments; } // 注意CreateProcessW的第二个参数需要可写的字符串 wchar_t* writableCmdLine new wchar_t[fullCmdLine.length() 1]; wcscpy_s(writableCmdLine, fullCmdLine.length() 1, fullCmdLine.c_str()); STARTUPINFOW si { sizeof(si) }; PROCESS_INFORMATION pi { 0 }; // 关键CREATE_NO_WINDOW 避免弹出黑框适合后台服务 // 如果目标程序需要控制台窗口请使用 CREATE_NEW_CONSOLE BOOL success CreateProcessW( NULL, // 应用程序名包含在命令行中 writableCmdLine, // 命令行必须可写 NULL, NULL, FALSE, CREATE_NO_WINDOW, // 创建标志 NULL, NULL, si, pi ); delete[] writableCmdLine; if (success) { hProcess pi.hProcess; CloseHandle(pi.hThread); // 立即关闭不需要的线程句柄 Log(Target process started successfully. PID: std::to_string(pi.dwProcessId)); return true; } else { DWORD err GetLastError(); Log(Failed to start process. Error code: std::to_string(err)); return false; } } // 终止目标进程 void TerminateTargetProcess() { if (hProcess hProcess ! INVALID_HANDLE_VALUE) { Log(Terminating target process...); if (!TerminateProcess(hProcess, 1)) { DWORD err GetLastError(); Log(TerminateProcess failed. Error: std::to_string(err)); } // 等待进程真正结束 WaitForSingleObject(hProcess, 5000); CloseHandle(hProcess); hProcess NULL; } } public: ProcessWatchdog(const WatchdogConfig cfg) : config(cfg), stopRequested(false), hProcess(NULL), restartCount(0), consecutiveStableTime(0) {} ~ProcessWatchdog() { Stop(); } void Run() { Log(Watchdog started. Monitoring: std::string(config.targetPath.begin(), config.targetPath.end())); while (!stopRequested restartCount config.maxRestarts) { // 步骤1启动进程 if (!StartTargetProcess()) { Log(Failed to start target. Will retry after backoff.); restartCount; unsigned int delay CalculateBackoffDelay(); Log(Backoff delay: std::to_string(delay) ms); std::this_thread::sleep_for(std::chrono::milliseconds(delay)); continue; } // 步骤2监控进程 DWORD waitTime 1000; // 检查间隔1秒 bool processExited false; unsigned int runningTime 0; while (!stopRequested !processExited) { DWORD waitResult WaitForSingleObject(hProcess, waitTime); if (waitResult WAIT_OBJECT_0) { // 进程已退出 DWORD exitCode; GetExitCodeProcess(hProcess, exitCode); Log(Target process exited with code: std::to_string(exitCode)); CloseHandle(hProcess); hProcess NULL; processExited true; // 判断是否稳定运行了足够长时间以重置计数器 if (runningTime config.stableThresholdMs) { Log(Process was stable for std::to_string(runningTime) ms. Resetting restart counter.); restartCount 0; } else { restartCount; } } else if (waitResult WAIT_TIMEOUT) { // 进程仍在运行 runningTime waitTime; // 这里可以添加心跳日志例如每分钟打印一次 if (runningTime % 60000 waitTime) { Log(Target process is alive. PID: std::to_string(GetProcessId(hProcess)) , Uptime: std::to_string(runningTime / 1000) s); } } else { // 等待出错 DWORD err GetLastError(); Log(Error waiting for process. Error: std::to_string(err)); processExited true; restartCount; } } // 步骤3进程退出后的处理 if (processExited !stopRequested restartCount config.maxRestarts) { unsigned int delay CalculateBackoffDelay(); Log(Restarting in std::to_string(delay) ms (Restart attempt: std::to_string(restartCount) )); std::this_thread::sleep_for(std::chrono::milliseconds(delay)); } } if (restartCount config.maxRestarts) { Log(Maximum restart attempts ( std::to_string(config.maxRestarts) ) reached. Watchdog stopping.); } Log(Watchdog stopped.); } void Stop() { stopRequested true; TerminateTargetProcess(); } }; int main() { // 配置看门狗 WatchdogConfig config; config.targetPath LC:\\MyApps\\MyService.exe; // 替换为你的目标程序路径 config.arguments L--port 8080 --config config.json; config.maxRestarts 10; config.baseDelayMs 1000; // 1秒 config.maxDelayMs 3600000; // 1小时 config.stableThresholdMs 60000; // 稳定运行60秒后重置计数器 ProcessWatchdog watchdog(config); // 设置控制台CtrlC处理以便优雅停止看门狗 SetConsoleCtrlHandler([](DWORD ctrlType) - BOOL { if (ctrlType CTRL_C_EVENT) { std::cout \nCtrlC received. Stopping watchdog... std::endl; // 注意这里无法直接访问watchdog对象实际应用中可能需要全局变量或更复杂的设计 // 本例中我们仅演示实际停止需通过其他IPC机制 return TRUE; } return FALSE; }, TRUE); std::cout Press CtrlC to stop the watchdog. std::endl; watchdog.Run(); return 0; }代码关键点解析可写命令行参数第53行我们动态分配了writableCmdLine数组来满足CreateProcessW的要求这是避免程序崩溃的细节。句柄管理第68行在成功创建进程后立即关闭了pi.hThread。进程句柄pi.hProcess则在进程退出后第108行或终止进程时TerminateTargetProcess函数内关闭。防止句柄泄漏是Windows编程的基本功。监控循环第89-124行的监控循环是核心。它使用带超时的WaitForSingleObject允许我们在等待期间响应停止请求stopRequested标志。同时它累计进程的运行时间runningTime用于判断是否达到稳定阈值。指数退避CalculateBackoffDelay函数实现了退避计算。每次重启失败或进程未达稳定阈值就退出restartCount增加延迟时间翻倍直到上限。稳定阈值重置第113-117行如果进程运行时间超过stableThresholdMs例如60秒我们认为它“稳定”了于是将restartCount重置为0。这避免了因偶发性崩溃导致延迟时间无限增长的问题。优雅停止Stop()方法设置stopRequested标志并终止目标进程。主函数中尝试设置了CtrlC处理器虽然在这个简单示例中无法直接停止对象因为它在另一个线程但展示了如何接收系统信号。5. 进阶话题提升生产环境可靠性上面的代码是一个功能完整的看门狗但对于严苛的生产环境还有几个关键点需要加强。5.1 使用Job对象管理进程树如前所述简单的进程监控可能无法处理子进程。Windows Job对象作业对象是一个强大的内核对象可以将一个进程及其所有未来创建的子进程关联到一个“作业”中进行统一管理。使用Job对象的主要优势进程树管理可以确保作业内的所有进程在作业结束时被终止。资源限制可以设置CPU时间、内存、句柄数等限制防止程序失控。通知机制可以收到作业内进程结束的通知。集成Job对象到看门狗的步骤创建Job对象CreateJobObject。在CreateProcess时设置dwCreationFlags包含CREATE_SUSPENDED挂起创建和CREATE_BREAKAWAY_FROM_JOB允许进程脱离当前作业如果看门狗本身也在一个作业中的话通常不需要。创建进程后将进程分配给Job对象AssignProcessToJobObject。恢复挂起的进程ResumeThread。等待Job对象而不是单个进程句柄WaitForSingleObjecton Job Handle。当作业内最后一个进程退出时作业对象会变为有信号状态。这比监控单个进程更彻底但实现也更复杂。你需要决定是监控主进程还是监控整个作业。5.2 作为Windows服务运行控制台程序容易被意外关闭且不便于管理。将看门狗注册为Windows服务是生产环境的标配。服务化带来的好处自动启动可以设置为系统启动时自动运行。服务恢复在服务属性中可以配置“第一次失败”、“第二次失败”、“后续失败”后的操作包括“重新启动服务”。这为看门狗自身提供了另一层保护。统一管理可以通过服务管理器services.msc启动、停止、查看状态。你需要实现服务主函数ServiceMain、服务控制处理器HandlerEx并在main函数中调用StartServiceCtrlDispatcher。代码结构会发生变化但核心的进程监控和重启逻辑可以封装在一个类中由服务控制逻辑调用。5.3 完善的日志与状态报告打印到控制台的信息在服务模式下是看不到的。一个健壮的看门狗需要将日志写入文件或系统事件日志Event Log。文件日志使用日志库如spdlog或自己实现滚动文件写入。记录每次启动、退出、重启、错误等信息并带上时间戳和进程ID。事件日志使用ReportEvent函数向Windows事件日志写入信息。这对于系统管理员在事件查看器中监控服务状态非常有用。状态文件可以定期将看门狗的状态如监控的PID、重启次数、运行时长写入一个JSON或文本文件供其他监控工具如Zabbix, Prometheus采集。5.4 配置文件与热重载将配置如目标路径、参数、重启策略参数硬编码在代码中很不灵活。应该从配置文件如JSON, XML, YAML中读取。更进一步可以实现配置热重载在程序运行时监控配置文件的变化并重新加载配置无需重启看门狗服务。这可以通过在监控循环中定期检查配置文件的最后修改时间来实现。如果文件有变化则解析新配置并更新内部变量。注意更新配置时需要线程安全地操作。6. 常见问题排查与实战技巧即使代码写对了在实际部署中还是会遇到各种奇怪的问题。这里记录一些我踩过的坑和解决方法。6.1 权限问题进程创建失败错误代码5如果目标程序路径需要管理员权限或者看门狗运行在普通用户权限下而目标程序需要更高权限CreateProcess会失败GetLastError()返回5拒绝访问。解决方案将看门狗程序也以管理员身份运行对于开发测试可行生产环境不推荐。推荐方案调整目标程序的权限要求或者将看门狗注册为以LocalSystem或特定高权限账户运行的服务。在服务配置中指定合适的账户。6.2 路径与工作目录问题CreateProcess的lpCurrentDirectory参数默认为NULL这意味着新进程的工作目录与看门狗进程相同。如果目标程序使用相对路径访问文件如./config.ini这可能导致找不到文件。解决方案在STARTUPINFO中或CreateProcess的参数中明确指定工作目录通常设置为目标程序所在的目录。在目标程序中始终使用绝对路径或者通过启动参数传递文件路径。6.3 句柄泄漏与资源耗尽这是最隐蔽的问题。如果看门狗在每次重启后没有正确关闭前一个进程的句柄句柄数会不断累积最终导致系统句柄耗尽CreateProcess失败。排查与解决使用Process Explorer或任务管理器查看“句柄”列监控看门狗进程的句柄数。正常情况下它应该稳定在一个很小的范围几个到几十个。确保在以下时机调用CloseHandle创建进程后立即关闭不需要的线程句柄pi.hThread。进程退出或强制终止后关闭进程句柄pi.hProcess。如果使用了Job对象在最后也要关闭Job句柄。在代码中所有CreateProcess、OpenProcess、CreateJobObject等调用附近检查是否有配对的CloseHandle。6.4 进程“假死”监控WaitForSingleObject只能检测进程是否结束但无法检测进程是否“假死”即进程还在但不响应、死循环。对于需要检测假死的场景需要额外的健康检查机制。常用方法心跳机制目标进程定期向看门狗报告“我还活着”通过命名管道、共享内存、TCP Socket、信号量等进程间通信方式。如果看门狗在超时时间内未收到心跳则判定为假死主动终止并重启。性能计数器使用Windows性能计数器PDH API监控目标进程的CPU使用率、IO等。如果长时间CPU占用率为0且无IO可能是假死。应用层健康检查如果目标程序是网络服务看门狗可以定期向其发送一个简单的HTTP请求或TCP Ping检查是否响应。实现心跳机制会显著增加复杂度需要根据业务的重要性来决定是否必要。6.5 开机自启动与依赖服务在生产服务器上看门狗需要随系统启动。如果它监控的服务依赖于其他服务如数据库、消息队列则需要处理启动顺序。解决方案作为Windows服务在服务的属性中设置“启动类型”为“自动”并在“依赖关系”选项卡中添加所依赖的服务。这样Windows服务管理器会确保依赖服务先启动。延迟启动可以在看门狗启动后先等待一段时间或轮询检查依赖服务是否就绪然后再启动目标程序。简单的等待可以用Sleep更可靠的检查是尝试连接依赖服务如TCP端口扫描。6.6 调试技巧调试一个守护进程的守护进程有点绕。一些有用的技巧日志日志还是日志在关键决策点创建进程前、等待返回后、重启前都打印详细的日志包括时间、PID、错误码。这是线上排查问题的唯一依据。附加调试器如果看门狗以服务运行可以使用DebugView捕获OutputDebugString的输出。或者在开发时暂时以控制台模式运行观察输出。模拟崩溃编写一个简单的测试程序随机运行一段时间后自己退出ExitProcess或崩溃如访问非法地址用来看门狗是否能正确重启。使用Process Explorer这个Sysinternals工具比任务管理器强大得多。你可以用它查看进程树、句柄、DLL、线程状态非常适合诊断进程创建、终止和资源泄漏问题。最后记住看门狗不是万能的。它只是一种提高可用性的补救措施。最根本的还是要尽量提高目标程序本身的稳定性和健壮性。看门狗应该被看作是系统防御的最后一道防线而不是掩盖问题的创可贴。