如何fork Qt6程序?困境下的技术探索与实现难题
在Qt应用中动态创建新运行实例的探索(fork方式)
我已经通过谷歌全面搜索过这个主题,得到的结论是“不要这么做”。但我目前进退两难,所以打算在彻底放弃“fork Qt应用”这个思路前,把它作为深入理解Qt初始化本质的练习,彻底探索一遍。
前提需求
我需要动态创建当前应用的新运行实例,新实例需与父实例同时运行。
我希望实现一个用于fork当前运行应用的函数,它需足够鲁棒,能处理所有可能存在的状态:
- 应用的实例化方式未知,最常见的两种是:应用主函数在
QTest::testCase()中实例化,或在int main(int argc, char *argv[])入口中实例化。 - 未知Qt事件循环是否已启动(通过QApplication/QTest::qExec等方式)。
- 未知运行的操作系统或使用的图形子系统。
- 未知应用目前已交互过的dbus、文件句柄、OpenGL上下文、信号或其他资源。
- fork发生后,父应用需继续无中断运行。
- 子进程需安全重置状态,通过实例化QApplication并构建UI等方式重新开始。
初步思路
- 父Qt6应用已处于运行状态,且已建立了复杂的状态。
- 对应用执行fork操作:
- 父进程保持原有状态不变。
- 子进程拥有父进程所有状态的过期副本。
- 在子进程中关闭所有过期状态。
- 启动新的QApplication并设置子进程所需的状态。
遇到的核心问题
该方法存在诸多问题,但核心问题在于:逐一追踪并成功关闭所有资源就像打地鼠游戏一样永无止境,尤其跨平台和不同Qt版本下,成功的可能性微乎其微。我的初步实现就完全体现了这一点:在清理旧状态后,子进程中实例化QPushButton时会永久挂起。
后续思路探索
我的下一个思路是通过某种方式复制或调用exit()系统调用的代码,让其运行到操作系统终止进程的阶段。似乎有大量exec*()、fork*()和posix_*()调用可实现类似功能,但这会牺牲平台独立性。最坏情况下,我们会采用这种方式,为所有关注的平台实现平台相关的方法。
请注意,使用
QProcess不可行,因为我们要运行的代码与当前运行的程序并非独立可执行文件,且我们无法控制父进程的包装/实例化方式(例如代码可能在接管了命令行参数的QTest中启动)。
初步尝试代码
testFork.hpp
#ifndef TESTFORK_HPP #define TESTFORK_HPP #include "test/Common.hpp" class TestFork:public QObject { Q_OBJECT private slots: void test(); }; #endif // TESTFORK_HPP
testFork.cpp
#include "TestFork.hpp" #include <QDebug> #include <QApplication> #include <QPushButton> #include <QTimer> #include <QAbstractEventDispatcher> #include <QOpenGLContext> #include <QtDBus/QDBusConnection> #include <csignal> #include <unistd.h> // For fork #include <sys/wait.h> // For waitpid void TestFork::test() { qDebug() << "TEST FORK"; pid_t pid = fork(); if (pid < 0) { qCritical() << "Fork failed!"; return; } else if (pid == 0) { qDebug() << "In child process A1a"; QDBusConnection::disconnectFromBus("session"); qDebug() << "In child process A1b"; QDBusConnection::disconnectFromBus("system"); // Reset OpenGL contexts if they exist if (QOpenGLContext::currentContext()) { qDebug() << "In child process A6a"; QOpenGLContext::currentContext()->doneCurrent(); qDebug() << "In child process A6b"; QOpenGLContext::currentContext()->deleteLater(); } qDebug() << "In child process A7a"; // Reset signal handlers to default signal(SIGPIPE, SIG_DFL); qDebug() << "In child process A7b"; signal(SIGCHLD, SIG_DFL); qDebug() << "In child process A8"; /* Commented out so we can see log output but does not seem to make a difference. close(0); // Close standard input close(1); // Close standard output close(2); // Close standard error */ qDebug() << "In child process A9"; for (int fd = 3; fd < 1024; ++fd) { close(fd); } if (QAbstractEventDispatcher::instance() != nullptr) { qDebug() << "An existing event loop is running, exiting it."; QCoreApplication::exit(); // End the existing event loop if running } int argc = 0; char **argv = nullptr; qDebug() << "In child process B1"; QApplication childApp(argc, argv); qDebug() << "In child process B2"; QPushButton *button = new QPushButton("Terminate Child"); qDebug() << "In child process C"; QObject::connect(button, &QPushButton::clicked, &childApp, &QApplication::quit); qDebug() << "In child process D"; QTimer::singleShot(100, [button]() { qDebug() << "In child process E"; button->show(); qDebug() << "In child process F"; }); qDebug() << "In child process G"; childApp.exec(); qDebug() << "In child process H"; _exit(0); // Ensure clean exit of the child process } else { qDebug() << "In parent process"; int status; waitpid(pid, &status, 0); } }
内容的提问来源于stack exchange,提问作者Mr. Developerdude
相关产品推荐
相关产品推荐

