首页 文章

终止在QThread中运行的正在进行的QProcess? [重复]

提问于
浏览
1

这个问题在这里已有答案:

如何终止在QThread中运行并被另一个QThread删除的正在进行的QProcess?我甚至插入了一个QMutex extCmdProcessLock,它应该在extCmdProcess完成或超时之前避免破坏DbManager . 如果另一个线程在DbManager上调用delete,我会在“waitForStarted”上遇到分段错误 . 我不能使用信号(我认为)因为我在顺序数据处理中使用外部命令 . 非常感谢您的帮助!

DbManager::extCmd(){
    ...
    QMutexLocker locker(&extCmdProcessLock);
    extCmdProcess = new QProcess(this);
    QString argStr  += " --p1=1"
                    +  " --p2=3";
    extCmdProcess->start(cmd,argStr.split(QString(" ")));
    bool startedSuccessfully = extCmdProcess->waitForStarted();
    if (!startedSuccessfully) {
       extCmdProcess->close();
       extCmdProcess->kill();
       extCmdProcess->waitForFinished();
       delete extCmdProcess;
       extCmdProcess = NULL;
       return;
    }
    bool successfullyFinished = extCmdProcess->waitForFinished(-1);
    if (!successfullyFinished) {
       qDebug() << "finishing failed"; // Appendix C
       extCmdProcess->close();
       extCmdProcess->kill();
       extCmdProcess->waitForFinished(-1);
       delete extCmdProcess;
       extCmdProcess = NULL;
       return;
   }
   extCmdProcess->close();
   delete extCmdProcess;
   extCmdProcess = NULL;
}

DbManager::~DbManager(){
    qDebug() << "DB DbManager destructor called.";   
    QMutexLocker locker(&extCmdProcessLock);
    if (extCmdProcess!= NULL){
       this->extCmdProcess->kill(); // added after Appendix A
       this->extCmdProcess->waitForFinished();
    }
}

Appendix A: 我也收到错误"QProcess: Destroyed while process is still running."并且我读到这可能意味着在waitForStarted()命令未完成时执行来自我的其他线程的"delete dbmanager"调用 . 但我真的想知道为什么我的析构函数中的kill()命令没有解决这个问题 .

Appendix B: 根据评论,添加 waitForFinished() . 遗憾的是,QProcess终止仍然无法正常关闭,分段错误发生在 waitForStarted()start() 本身 .

#0  0x00007f25e03a492a in QEventDispatcherUNIX::registerSocketNotifier () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#1  0x00007f25e0392d0b in QSocketNotifier::QSocketNotifier () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#2  0x00007f25e0350bf8 in ?? () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#3  0x00007f25e03513ef in ?? () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#4  0x00007f25e03115da in QProcess::start () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#5  0x0000000000428628 in DbManager::extCmd()
#6  0x000000000042ca06 in DbManager::storePos ()
#7  0x000000000044f51c in DeviceConnection::incomingData ()
#8  0x00000000004600fb in DeviceConnection::qt_metacall ()
#9  0x00007f25e0388782 in QObject::event () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#10 0x00007f25e0376e3f in QCoreApplicationPrivate::notify_helper () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#11 0x00007f25e0376e86 in QCoreApplication::notify () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#12 0x00007f25e0376ba4 in QCoreApplication::notifyInternal () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#13 0x00007f25e0377901 in QCoreApplicationPrivate::sendPostedEvents () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#14 0x00007f25e03a4500 in QEventDispatcherUNIX::processEvents () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#15 0x00007f25e0375e15 in QEventLoop::processEvents () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#16 0x00007f25e0376066 in QEventLoop::exec () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#17 0x00007f25e0277715 in QThread::exec () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#18 0x00007f25e027a596 in ?? () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#19 0x00007f25df9b43f7 in start_thread () from /lib/libpthread.so.0
#20 0x00007f25def89b4d in clone () from /lib/libc.so.6
#21 0x0000000000000000 in ?? ()

Appendix C: 调试输出向我显示错误消息:QProcess:进程仍在运行时被销毁 . 出现完成失败输出时总是出现 . 这意味着我的锁或/和kill尝试保护QProcess失败 . 我想知道的问题:

a)如果创建一个QProcess对象并启动它,我的 extCmdProcessLock 是否已解锁?我已经尝试使用正常的 lock() 调用而不是 QMutexLoader ,但没有运气 .

b)文档说如果我以这种方式使用QProcess,主线程将被停止 . 它们真的是指主线程或启动QProcess的线程吗?我假设第二 .

c)QProcess是否在多线程环境中不可用?如果两个线程创建一个QProcess对象并运行它,它们会干扰吗?也许对象在某种程度上是静态的?

感谢您填写知识泄漏的任何帮助 . 我真的希望解决这个难题 .

Appendix D: 从任何线程中删除任何delete和deleteLater()后,我的QProcess仍然被粉碎 .

#0  0x00007fc94e9796b0 in QProcess::setProcessState () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#1  0x00007fc94e97998b in QProcess::waitForStarted () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#2  0x00007fc94e979a12 in QProcess::waitForFinished () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#3  0x0000000000425681 in DbManager::extCmd()
#4  0x0000000000426fb6 in DbManager::storePos ()
#5  0x000000000044d51c in DeviceConnection::incomingData ()
#6  0x000000000045fb7b in DeviceConnection::qt_metacall ()
#7  0x00007fc94e9f4782 in QObject::event () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#8  0x00007fc94e9e2e3f in QCoreApplicationPrivate::notify_helper () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#9  0x00007fc94e9e2e86 in QCoreApplication::notify () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#10 0x00007fc94e9e2ba4 in QCoreApplication::notifyInternal () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#11 0x00007fc94e9e3901 in QCoreApplicationPrivate::sendPostedEvents () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#12 0x00007fc94ea10500 in QEventDispatcherUNIX::processEvents () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#13 0x00007fc94e9e1e15 in QEventLoop::processEvents () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#14 0x00007fc94e9e2066 in QEventLoop::exec () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#15 0x00007fc94e8e3715 in QThread::exec () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#16 0x00007fc94e8e6596 in ?? () from /usr/local/Trolltech/Qt-4.7.4/lib/libQtCore.so.4
#17 0x00007fc94e0203f7 in start_thread () from /lib/libpthread.so.0
#18 0x00007fc94d5f5b4d in clone () from /lib/libc.so.6
#19 0x0000000000000000 in ?? ()

2 回答

  • 6

    使用QThread来管理正在运行的进程真的很糟糕 . 我一次又一次地看到它,这是关于如何正确编写异步应用程序的一些基本误解 . 进程与您自己的应用程序分开 . QProcess提供了一组漂亮的信号,可在成功启动,启动失败和完成时通知您 . 只需将这些信号挂钩到您的QObject派生类的实例中的插槽中,您就可以全部设置 .

    如果应用程序中的线程数量明显超过平台上可用的核心数/超线程数,或者线程数与某些不相关的运行时因子(如正在运行的子进程数)相关联,则设计很糟糕 .

    看到我的其他other answer .

    您可以在堆上创建QProcess,作为监视QObject的子级 . 您可以将QProcess的finished()信号连接到它自己的deleteLater()插槽,这样它就会在完成时自动删除 . 监视QObject应该强制终止任何剩余的正在运行的进程,当它被自己销毁时,比如应用程序关闭的结果 .

    进一步的问题是如何执行不受控制的长时间运行的函数,比如没有异步API的数据库查询,影响最小,当穿插有良好异步API的东西时,例如QProcess .

    规范的方法是:在必要的地方同步执行,否则异步执行 . 您可以通过调用其 deleteLater() 插槽来停止控制对象和任何正在运行的进程 - 通过信号/插槽连接,或者如果您想在安全地越过线程边界时直接执行 QMetaObject::invokeMethod() . 这是使用尽可能少的阻塞调用的主要好处:您可以控制处理并且可以在某些时候停止它 . 使用纯粹的阻塞实现,没有办法阻止它使用一些标志变量并将代码用于测试 .

    只要事件循环在QObject所在的线程中旋转, deleteLater() 就会被处理 . 这意味着它将在数据库查询调用之间获得机会 - 实际上是在进程运行的任何时候 .

    未经测试的代码:

    class Query : public QObject
    {
      Q_OBJECT
    public:
      Query(QObject * parent = 0) : QObject(parent) {
        connect(process, SIGNAL(error(QProcess::ProcessError)), SLOT(error()));
        connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(finished(int,QProcess::ExitStatus)));
      }
      ~Query() { process.kill(); }
      void start() {
        QTimer::singleShot(0, this, SLOT(slot1()));
      }
    protected slots:
      void slot1() {
        // do a database query
        process.start(....);
        next = &Query::slot2;
      }
    protected:
      // slot2 and slot3 don't have to be slots
      void slot2() {
        if (result == Error) {...}
        else {...}
        // another database query
        process.start(...); // yet another process gets fired
        next = &Query::slot3;
      }
      void slot3() {
        if (result == Error) {...}
        deleteLater();
      }
    
    protected slots:
      void error() {
        result = Error;
        (this->*next)();
      }
      void finished(int code, QProcess::ExitStatus status) {
        result = Finished;
        exitCode = code;
        exitStatus = status;
        (this->*next)();
      }
    private:
      QProcess process; 
      enum { Error, Finished } result;
      int exitCode;
      QProcess::ExitStatus exitStatus;
      void (Query::* next)();
    };
    

    就个人而言,我会检查您使用的数据库是否具有异步API . 如果它没有,但如果客户端库有可用的源,那么我会做一个最小的端口来使用Qt的网络堆栈使其异步 . 它会降低开销,因为你不再需要每个数据库连接一个线程,并且当你接近使CPU饱和时,开销就不会上升:通常,要使CPU饱和,你需要很多很多线程,因为他们大多闲着 . 使用异步接口,上下文切换的数量会下降,因为线程将处理来自数据库的一个数据包,并且可以立即处理来自不同连接的另一个数据包,而不必进行上下文切换:执行保持在该线程的事件循环 .

  • 1

    QProcess::waitForStarted只是表示您的流程已经启动 . extCmd()方法中的互斥锁被解锁,因为您没有在此方法中等待QProcess::waitForFinished . 子进程仍在运行时,您将 exit 此方法 .

    如果你想使用fire&forget类型的执行,我只使用QProcess::startDetached

相关问题