QThread
has, basically, a long-standing API bug: it isn't always in a destructible state. In C++, an object is considered to be in destructible state when it's safe to invoke its destructor. Destructing a running QThread
is an error. A QThread
is merely a thread controller, it's not the "thread" itself. Think of how QFile
acts: you can destruct it at any time, whether it's open or not. It truly encapsulates the notion of a file as a resource. A QThread
is too thin of a wrapper around the native (system) thread: when you destruct it, it does not terminate nor dispose of the native thread if there is one. This is a resource leak (threads are OS resources), and people trip over this issue over and over again.
When the application's main()
function returns, your implementation of the C/C++ runtime library happens to terminate all of the application's threads, effectively terminating the entirety of the application. Whether this is the behavior you desire is up to you. You're supposed to quit()
and wait()
your event-loop-running thread. For threads without an event loop, quit()
is a no-op and you must implement your own quit flag. You must wait()
on the thread before you destruct it. This is to prevent race conditions.
Below is a safe wrapper for QThread
. It is a final class, since you can't reimplement run
. This is important, since a reimplementation of run could be done in such a way that makes quit
a no-op, breaking the contract of the class.
#include <QThread>
#include <QPointer>
class Thread : public QThread {
using QThread::run; // final
public:
Thread(QObject * parent = 0) : QThread(parent) {}
~Thread() { quit(); wait(); }
};
class ThreadQuitter {
public:
typedef QList<QPointer<Thread>> List;
private:
List m_threads;
Q_DISABLE_COPY(ThreadQuitter)
public:
ThreadQuitter() {}
ThreadQuitter(const List & threads) : m_threads(threads) {}
ThreadQuitter(List && threads) : m_threads(std::move(threads)) {}
ThreadQuitter & operator<<(Thread* thread) {
m_threads << thread; return *this;
}
ThreadQuitter & operator<<(Thread& thread) {
m_threads << &thread; return *this;
}
~ThreadQuitter() {
foreach(Thread* thread, m_threads) thread->quit();
}
};
It could be used as follows:
#include <QCoreApplication>
int main(int argc, char ** argv) {
QCoreApplication app(argc, argv);
QObject worker1, worker2;
Thread thread1, thread2;
// Style 1
ThreadQuitter quitter;
quitter << thread1 << thread2;
// Style 2
ThreadQuitter quitterB(ThreadQuitter::List() << &thread1 << &thread2);
//
worker1.moveToThread(&thread1);
worker2.moveToThread(&thread2);
thread1.start();
thread2.start();
QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection);
return app.exec();
}
Upon return from main
, the thread quitter will quit()
all worker threads. This allows the threads to wind down in parallel. Then, thread2.~Thread
will wait for that thread to finish, then thread1.~Thread
will do the same. The threads are now gone, the objects are threadless and can be safely destructed: worker2.~QObject
is invoked first, followed by worker1.~QObject
.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…