如何优雅地退出多线程应用程序?

时间:2017-09-14 10:07:42

标签: c++ multithreading qt exit

如果我有一个Qt应用程序(使用QCoreApplication),并且此应用程序启动了一些永久线程,那么关闭应用程序的正确方法是什么?

在其中一个主题中运行QCoreApplication::quit()是否可以?这会导致其他线程被优雅地终止(并且所有包含的对象'析构函数被调用,而不是被强制杀死)?

更多细节来解释线程的性质:它们是预定义的并从启动运行,并且在应用程序退出之前不会停止,即它们是永久性的。它们运行自己的事件循环,并通过信号和插槽与其他线程通信。这些是正常的线程,而不是基于任务的并发。

5 个答案:

答案 0 :(得分:4)

大多数长跑'线程主要'函数的形式有点像:

while (doWork) {
    work();
}

doWork是std::atomic<bool>

当主线程要退出时,它会在所有仍处于活动状态的线程上设置myThread.doWork = false,这样就可以在它们准备就绪时掉线。

通过在主线程上调用myThread.wait(),它会阻塞,直到您告知停止工作的线程实际停止。 在为所有线程执行此操作时,当主线程离开main()时,它仍然是唯一仍在运行的线程。

旁注:     如果你不得不等待工作被推到它上面,你可能想要查看QWaitCondition类,这样你就可以在工作时和你希望它停止的时候唤醒你的线程。

答案 1 :(得分:3)

这在很大程度上取决于你如何使用线程。

如果您将它们用作单独的eventloops,而不是&#34;工作线程&#34;,只需通过退出QCoreApplication::aboutToQuit信号中的线程来停止:

QObject::connect(qApp, &QCoreApplication::aboutToQuit, thread, [thread](){
    thread->quit();
    thread->wait(1000);
});

(对于多个线程,首先退出所有线程,然后等待)

如果你将它们用作真正的workerthreads,你在循环中进行永久性工作等,你可以使用QThreads中断机制。在您的主题中执行:

while(!QThread::currentThread()->isInterruptionRequested()) {
    // code...
}

并以非常类似的方式退出:

QObject::connect(qApp, &QCoreApplication::aboutToQuit, thread, [thread](){
    thread->requestInterruption();
    thread->wait(1000);
});

答案 2 :(得分:1)

大多数现代平台的目标是在流程突然结束后“清理”。 这将结束所有线程,恢复所有内存,关闭所有打开的文件并恢复分配给进程的任何其他资源或句柄。

但不建议依赖它,并且当执行可能具有“持久”副作用(例如写入文件或与其他进程(包括共享内存)通信时可能无法终止时,可能无法轻松清理) 。文件可以保持半写,其他进程可以接收半完整消息或向未声明终止的进程发送消息。

在突然结束的进程中识别内存泄漏也很困难。

最佳做法总是会使所有线程得出已知的结论。

推荐的终止方法是定义一个或多个stop标志(通常为bool),这些标志将由“安全”点的线程检查以终止。 如果在stop条件中使用,那些std::atomic<>标志应为原子(std::mutex)或受wait()保护。

在该模型中,终止代码看起来像......

#include <iostream>
#include <atomic>
#include <mutex>
#include <thread>
#include <vector>

std::atomic<bool> stop_flag;
std::vector<std::thread> threads;

std::mutex cout_mutex;//std::cout is not natively synchronized.

void chug(size_t index){
    int i{0};
    while(!stop_flag){
        {
            std::lock_guard<std::mutex> guard{cout_mutex};
            std::cout<<index<<" : "<<i<<std::endl;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(10));//slow it down!
        ++i;
    }
}


//stop_all brings all the threads to a safe and known conclusion.
void stop_all(){
    stop_flag=true;
    for( auto& curr: threads){
        curr.join();
    }
}


int main() {
    const size_t num{10};
    for(size_t i=0;i<num;++i){
        threads.emplace_back(chug,i);
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(100));//Let it run!

    stop_all();

    return 0;
}

答案 3 :(得分:1)

对于问题解释的作者,我们可以缩小到确切的线程类型:

  

我有从预启动运行的预定义线程,直到没有停止   申请退出,即他们是永久性的。他们自己经营   事件循环并通过信号和插槽与其他线程通信。   这些是正常的线程,而不是基于任务的并发。我怎么能够   组织这种类型的多线程?

通过将具有预定义信号和插槽的对象“移动”到其方法应该运行的线程,可以很容易地在Qt中进行组织。

class Worker : public QObject
{
    Q_OBJECT
public:
    Worker(Load*);

signals:
    void produce(Result*);

public slots:
    void doWork(Load*);
};

void Worker::doWork(Load* pLoad)
{
    // here we can check if thread interruption is requested
    // if the work is of iterative long time type
    while(!QThread::currentThread()->isInterruptionRequested())
    {
        process(pLoad);
        if (pLoad->finished())
        {
           emit produce(pLoad->result()); // deliver the result
           return; // back to worker thread event loop to wait
        }
    }
    // interrupted before the load finished
    QThread::currentThread()->quit();
}


// { somewhere on the main thread
// main thread launches worker threads like this one
QThread thread;
Worker worker(new Load());
worker.moveToThread(&thread); // the worker will be leaving in the thread
// start!
thread.start();

// worker thread adds new workload on its thread
// through the thread event loop by invokeMethod
QMetaObject::invokeMethod(&worker, "doWork", Qt::AutoConnection,
                          Q_ARG(Load*, new Load));

// after all we want to quit the app
thread.requestInterruption(); // for faster finishing
// or
thread.quit(); // for finishing after the last data processed
thread.wait(); // for one thread wait but you can catch finished() 
      // signals from many threads (say, count until all signaled)

// ........
// quit the app
// ........
qApp->quit();
// } somewhere on main thread

它可能看起来有点像基于任务的并发,但线程上没有任务对象从队列中获取其负载。它只是演示了Qt工作线程通过信号和插槽进行通信。

答案 4 :(得分:0)

我同意@UKMonkey关于线程块的想法但是如果某些线程正在等待设备或内存并且条件等待不能保证线程退出,甚至它会阻止应用程序退出。 那么如何处理这些情况,QCoreApplication有 aboutToQuit()信号,你可以将它连接到一个插槽并强制你的线程退出并检查一个线程是否没有正常退出并纠正它的退出场景。