项目需要将算法放到独立的进程运行,一来避免了算法不稳定导致主程序崩溃,二来避免了多个算法库的依赖冲突。主程序和子程序之间可用 Socket、共享内存、管道等方式进程交互,复杂的也可以上 RPC 框架。
本来首先想到的是使用 QProcess 来启动子进程,但是启动之后想要结束或者是判断进程状态属实不方便,索性直接全用 Win32 的接口来操作。由于需求简单,只是一个主程序对应多个子进程的形式,所以也不需要设计什么进程调度,只需要启动、关闭和检测进程运行状态。分别查找对应的接口:
首先是启动进程,百度到主要有四种接口,参照:,为了方便创建进程时拿到对应的句柄,没用 CreateProcess,选择了 ShellExecuteEx,大致如下:
//初始化
SHELLEXECUTEINFOW se_info = { 0 };
//memset(&se_info, 0x00, sizeof(SHELLEXECUTEINFOW));
//in.required.此结构体字节大小
se_info.cbSize = sizeof(SHELLEXECUTEINFOW);
//in.SEE_MASK_NOCLOSEPROCESS用于指示hProcess成员接收到进程句柄。
se_info.fMask = SEE_MASK_NOCLOSEPROCESS;
//in.optional.父窗口的句柄,用于显示系统在执行此功能时可能产生的任何消息框。该值可以为NULL。
//se_info.hwnd = NULL;
//in.optional.要执行的动作,open打开指定lpFile文件,runas管理员身份启动应用程序
se_info.lpVerb = L"open";
//in.以空值结尾的字符串的地址
se_info.lpFile = reinterpret_cast<LPCWSTR>(info_path.utf16());
//in.optional.执行参数
se_info.lpParameters = reinterpret_cast<LPCWSTR>(info_arg.utf16());
//in.optional.工作目录,为NULL则使用当前目录
//se_info.lpDirectory = NULL;
//in.required.SW_HIDE隐藏该窗口并激活另一个窗口,打开的进程不会显示窗口
//因为hide不显示窗口,测试/调试时可以用SW_SHOW
se_info.nShow = info.visible ? SW_SHOW : SW_HIDE;
//out.如果设置了SEE_MASK_NOCLOSEPROCESS并且ShellExecuteEx调用成功,它将把该成员设置为大于32的值。
//如果函数失败,则将其设置为SE_ERR_XXX错误值,以指示失败的原因。
//se_info.DUMMYUNIONNAME;
//out.valid when SEE_MASK_NOCLOSEPROCESS 新启动的应用程序的句柄,如果未启动则为NULL
//se_info.hProcess;
qDebug()<<"execute exe."<<info.path<<info.key<<info.args;
//ShellExecuteEx创建的进程可以提权
//CreateProcess继承权限,但可以更好地控制
if(::ShellExecuteExW(&se_info)){
if(se_info.hProcess != NULL){
auto hProcess = se_info.hProcess; //拿到句柄
qDebug()<<"execute success.";
}
}
qDebug()<<"execute fail."<<(intptr_t)(HINSTANCE)se_info.hInstApp;
启动之后就将启动参数和句柄放到列表中,主进程轮询检测子进程是否运行正常,如果异常则重新启动该进程。子进程也可以轮询检测主进程执行状态,异常则自动退出进程,我是通过启动参数的方式将主进程 pid 传递给的子进程,然后以 OpenProcess 接口来拿到对应的句柄。检测进程是否运行我是用 GetExitCodeProcess,大致如下:
//检查所有进程的状态,并重置计数
for(auto iter = processTable.begin(); iter != processTable.end(); iter++)
{
const ProcessInfo &node = iter.value();
DWORD exit_code;
//检测进程是否正常运行,把需要重启的重启(hProcess为进程句柄)
::GetExitCodeProcess(node.hProcess,&exit_code);
//如果进程尚未终止且函数成功,则返回的状态为STILL_ACTIVE
if(exit_code!=STILL_ACTIVE){
//如果异常可以选择自动重启进程
}
}
最后,主程序也可以主动关闭进程,使用 TerminateProcess
//从列表取对应的进程句柄进程关闭
if(::TerminateProcess(hProcess,-1)){
qDebug()<<"terminate success.";
}
(2021-9-22)补充:每个启动参数最好用双引号括起来,避免解析时遇到空格被拆开了。
我做的一个小 Demo:
github 链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qt/MultiProcess_Win