Qt多线程之QThread

Qt多线程之QThread

线程基本介绍

1.1 线程与进程

进程:是各种资源的容器,它定义了一个地址空间作为基本的执行环境

线程:是一个指令执行的序列,它可以直接访问所属进程中的资源。每个进程都至少有一个线程,每个线程再任一时刻都一定属于某一特定的进程。

  • 引用自《windows内核原理与实现》

http://www.ruanyifeng.com/blog/2013/04/processesandthreads.html

https://zhuanlan.zhihu.com/p/52612180

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;
}
注意事项
  1. 不能连续调用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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

豆芽不是菜33

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值