Qt多线程之QThread
线程基本介绍
1.1 线程与进程
进程:是各种资源的容器,它定义了一个地址空间作为基本的执行环境
线程:是一个指令执行的序列,它可以直接访问所属进程中的资源。每个进程都至少有一个线程,每个线程再任一时刻都一定属于某一特定的进程。
- 引用自《windows内核原理与实现》
http://www.ruanyifeng.com/blog/2013/04/processesandthreads.html
2.2 何时需要使用多个线程
事件产生时被立即派发,而是列入到一个事件队列中,等待以后的某一个时刻发送。分配器会遍历事件队列,并且将入栈的事件发送到它们的目标对象当中,因此它们被称为事件循环。当有一个耗时程序被执行时,事件循环将会被堵塞直到耗时程序执行完毕,此时界面不会再响应鼠标、键盘、定时器事件,控件也不会刷新自己。此时就需要创建一个线程去执行耗时任务,使得主线程的事件循环不被堵塞。
2.3 使用线程的注意事项
- 不能跨线程操作GUI对象。子线程不能操作主线程中创建的控件。
- 线程安全。如果一个函数能够安全的同时被多个线程调用而得到正确的结果,那么,我们说这个函数是线程安全的。详细参考本章 锁;
创建线程
2.1 Qt说明书用法
2.1.1 继承QThread
class WorkerThread : public QThread
{
void run() override {
/* ... here is the expensive or blocking operation ... */
}
~WorkerThread() { finish(); }
void finish() {/* ... end run() ... */
workerThread.wait();
}
};
void MyObject::startWorkInAThread()
{
WorkerThread* workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);//看情况选用
workerThread->start();
}
2.1.2 不继承QThread
#include# <QThread>
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(const QString& parameter) {
/* ... here is the expensive or blocking operation ... */
}
};
class Controller : public QObject
{
Q_OBJECT
public:
QThread workerThread;
Controller() {
Worker* worker = new Worker;
worker->moveToThread(&workerThread);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);//看情况选用
workerThread.start();//启动事件循环
emit operate("3");
}
void finish()
{
workerThread.quit();//退出事件循环
workerThread.wait();
}
~Controller() {
finish();
}
signals:
void operate(const QString&);
};
2.1.3 两种形式对比
继承QThread,重写run() | 不继承QThread,moveToThread | |
---|---|---|
实现机制不同 | 只有run()函数在新的线程中执行 | 通过qt事件循环机制,通过信号槽,让函数在新的线程中执行。 |
创建方式不同 | 需要继承QThread,破坏了类的结构 | 不需要继承QThread |
启动方式不同 | 调用start()函数就会运行一次run()函数 | 需要通过信号槽启动函数,可以重复启动多个在子线程工作的函数 |
2.1.4 源代码分析
1、继承QThread,重写run的运行过程。
直接调用派生类重写的函数。
2、讲解不继承QThread,线程运行机制。
使用moveToThread将对象从当前线程推到指定线程中,并在指定线程启动一个事件循环。
通过信号启动槽函数,同时发射多个信号时,槽函数会被放到事件队列中一一执行。
connect(this, &Controller::operate, worker, &Worker::doWork3);//在新的线程中执行
connect(this, &Controller::operate, worker, &Worker::doWork, Qt::QueuedConnection);//在新的线程中执行
connect(this, &Controller::operate, worker, &Worker::doWork, Qt::DirectConnection);//在主线程中执行
如果还有槽函数没有执行完毕,则无法使用quit退出事件循环
3、讲解不继承QThread,quit的作用。
退出事件循环。
class Q_CORE_EXPORT QThread : public QObject
{
Q_OBJECT
...
protected:
virtual void run()
{
(void)exec();
};
...
};
int QThread::exec()
{
...
QEventLoop eventLoop;
int returnCode = eventLoop.exec();
...
return returnCode;
}
void QThread::start(Priority priority)
{
...
...
d->handle = CreateThread(nullptr, d->stackSize,
reinterpret_cast<LPTHREAD_START_ROUTINE>(QThreadPrivate::start),
this, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&d->id));
}
void QThread::exit(int returnCode)
{
...
for (int i = 0; i < d->data->eventLoops.size(); ++i) {
QEventLoop* eventLoop = d->data->eventLoops.at(i);
eventLoop->exit(returnCode);
}
}
void QThread::quit()
{
exit();
}
unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void* arg) noexcept
{
QThread* thr = reinterpret_cast<QThread*>(arg);
...
emit thr->started(QThread::QPrivateSignal());//发射启动信号
...
thr->run();//调用run() 函数
finish(arg);
return 0;
}
2.2 便捷启动一个线程
2.2.1 创建、启动
#include# <QtCore/QCoreApplication>
#include# <QThread>
#include# <QDebug>
void work1();
void work2(int var1);
int main(int argc, char* argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "Main ThreadID:" << QThread::currentThreadId();
QThread* p = QThread::create(work1);
QThread* p2 = QThread::create(work2, 3);
QObject::connect(p2, SIGNAL(finished()), p2, SLOT(deleteLater()));//看情况选用
QObject::connect(p, SIGNAL(finished()), p, SLOT(deleteLater()));//看情况选用
p->start();
p2->start();
return a.exec();
}
void work1()
{
qDebug() << "sub1 ThreadID:" << QThread::currentThreadId();
}
void work2(int var1)
{
qDebug() << "sub2 ThreadID:" << QThread::currentThreadId() << ",I have a Var:" << var1;
}
注意事项
- 不能连续调用start()
2.2.2 源代码分析
使用了std::async
QThread* QThread::create(Function&& f)
{
...
return createThreadImpl(...);
}
QThread* QThread::createThreadImpl(std::future<void>&& future)
{
return new QThreadCreateThread(std::move(future));
}
class QThreadCreateThread : public QThread
{
public:
explicit QThreadCreateThread(std::future<void>&& future)
: m_future(std::move(future))
{
}
private:
void run() override
{
m_future.get();
}
std::future<void> m_future;
};
*启动类成员函数 QThread::create(&类名::函数, 类指针);
线程等待
3.1 等待线程执行结束方法
bool QThread::wait(unsigned long time = ULONG_MAX)
阻塞调用该函数所在的线程,直到子线程对象结束。
如果给定了等待时间,时间到达后函数返回false,并开始执行下一行程序。
如果没有给定时间,会一直等待下去。
3.2 测试代码
3.2.1 演示 不加等待
程序没有等待线程执行完毕直接执行了后面的代码。
3.2.2 演示 增加等待后
程序会等待线程执行完毕后才会运行后面的代码。
3.2.3 演示 增加等待timeout 效果
程序会等待指定时间后才会运行后面的代码。
#include# <QtCore/QCoreApplication>
#include# <QThread>
#include# <QDebug>
void work1();
void work2(int var1);
int main(int argc, char* argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "Main ThreadID:" << QThread::currentThreadId();
QThread* p = QThread::create(work1);
QObject::connect(p, SIGNAL(finished()), p, SLOT(deleteLater()));
p->start();
//bool waitResult = p->wait();
//bool waitResult = p->wait(400);
qDebug() << "sub thread end";
return a.exec();
}
void work1()
{
_sleep(3000);
qDebug() << "sub1 ThreadID:" << QThread::currentThreadId();
}
void work2(int var1)
{
qDebug() << "sub2 ThreadID:" << QThread::currentThreadId() << ",I have a Var:" << var1;
}
终止线程
4.1 退出一个一直在运行的线程
class WorkerThread : public QThread
{
public:
void finish()
{
m_isFinish = false;
wait();
};
void run() override {
m_isFinish = true;
while (m_isFinish)
{
/* ... here is the expensive or blocking operation ... */
}
}
private:
bool m_isFinish;
};
void MyObject::startWorkInAThread()
{
WorkerThread* workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);//看情况选用
workerThread->start();
workerThread->finish();
}
*如果是不继承QThread的方式,退出线程时需要额外调用quit() 退出事件循环
4.2 强制退出线程
void QThread::terminate()
QThread::wait()
不推荐该方式,仅再必要时调用。根据操作系统的调度策略,线程可能会立即终止,也可能不会立即终止。所以线程可以在其代码路径中的任何位置终止。
线程状态
5.1 结束
bool QThread::isFinished() const
5.2 正在运行
bool QThread::isRunning() const
锁
6.1 锁的作用
防止多个线程同时访问一个对象导致的每次计算结果不一致、不正确。
当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。
6.2 QMutex
void QMutex::lock();//如果另一个线程锁定了互斥锁,则该调用将一直阻塞,直到该线程将其解锁为止。
void QMutex::unlock();//解锁互斥锁
6.3 测试代码
6.3.1 演示 未上锁之前的结果
多次运行代码得到的计算结果不一致。
6.3.2 演示 上锁之后
多次运行代码得到的计算结果一致。
6.3.3 演示 死锁
线程阻塞不再往下运行。
#include# <QtCore/QCoreApplication>
#include# <QThread>
#include# <QDebug>
#include# <QMutex>
void work1();
void work2();
void counterAdd();
int counter;
QMutex qmutex;
int main(int argc, char* argv[])
{
QCoreApplication a(argc, argv);
counter = 0;
QThread* p = QThread::create(work1);
QThread* p2 = QThread::create(work2);
p->start();
p2->start();
p->wait();
p2->wait();
qDebug() << "counter num:" << counter;
return a.exec();
}
void work1()
{
for (int i = 0; i < 100; i++)
{
counterAdd();
_sleep(1);
}
}
void work2()
{
for (int i = 0; i < 100; i++)
{
counterAdd();
_sleep(1);
}
}
void counterAdd()
{
//qmutex.lock();
counter++;
//return;
//qmutex.unlock();
}
6.4 便利类QMutexLocker
6.4.1 说明
不需要手动加锁和解锁,对象会在销毁时自动解锁。
6.4.2 修改上例程序
1、演示 效果
void counterAdd()
{
QMutexLocker locker(&qmutex);
counter++;
//return;
}