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();
    }
}

附录 A:我还收到错误“QProcess: Destroyed while process is still running”。我读到这可能意味着我的另一个线程的“删除 dbmanager”调用在 waitForStarted() 命令尚未完成时执行。但我真的很想知道为什么我的析构函数中的 kill() 命令没有解决这个问题。

附录 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 ?? ()

附录 C:调试输出向我展示了错误消息:QProcess: Destroyed while process is still running。总是出现,当出现完成失败的输出时。这意味着我保护 QProcess 的锁定或/和终止尝试失败。我想知道的问题:

a)如果创建一个 QProcess 对象并启动它,我的extCmdProcessLock解锁了吗?我已经尝试使用普通lock()电话而不是QMutexLoader但没有运气。

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

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

感谢您在填补知识泄漏方面的任何帮助。我真的希望能解决这个难题。

附录 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 ?? ()
4

2 回答 2

6

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

如果您的应用程序中的线程数量可能大大超过平台上可用的内核/超线程数量,或者如果线程数量与一些不相关的运行时因素(如正在运行的子进程数量)相关联,那么这是一个糟糕的设计。

请参阅我的其他答案

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

进一步的问题是如何执行无法控制的长时间运行的函数,比如没有异步 API 的数据库查询,并且在穿插有良好异步 API 的事物(例如 QProcess)时影响最小。

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

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

未经测试的代码:

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 饱和,您需要很多很多线程,因为它们大多是空闲的。使用异步接口,上下文切换的数量会减少,因为线程会处理来自数据库的一个数据包,并且可以立即处理来自不同连接的另一个数据包,而无需进行上下文切换:执行停留在该线程的事件循环。

于 2012-05-31T22:06:50.760 回答
1

QProcess::waitForStarted只是表示您的进程已经开始。extCmd() 方法中的互斥锁随后被解锁,因为您没有在此方法中等待QProcess::waitForFinished 。您将在子进程仍在运行时退出此方法。

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

于 2012-05-28T06:28:14.970 回答