文章目录
简介
相关名词
- 同步Sync VS 异步Async
- 同步(手动查询):我调用一个功能,该功能没有结束前,我不断手动查询结果。
- 异步(主动通知):我调用一个功能,不需要知道该功能结果,该功能有结果后通知我。
- 阻塞Block VS 非阻塞Unblock
- 阻塞: 调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
- 非阻塞:调用我(函数),我(函数)立即返回,通知调用者。
- 回调
- 同步回调:阻塞(无需等待回调完成即可完成其执行)。
- 异步回调:非阻塞(无需等待回调完成即可完成其执行)。
- 进程 VS 线程 VS 协程
- 并发concurrency VS 并行parallelism
- 并发(交替进行):并发包含多线程,多线程是并发的一种实现方式。
- 并行(同时进行):多核处理器。
- 事件循环
QT
使用QThread类。
作用:用线程来处理那些耗时的后台操作,从而让主界面能及时响应用户的请求操作。
运行方式
QThread 的执行从 run()
函数的执行开始。
run()
函数通过调用exec()
函数来启动事件循环机制,并且在线程内部处理 Qt 的事件。
基础使用方法
QObject::moveToThread()
- 继承
QThread
类:子类化QThread,然后重写run函数: 旧方法,不推荐。- run() 中未调用 exec() 开启 even loop =》 run() 执行结束时,线程自动退出。
- 如果在 WorkerThread 的 run() 中使用了 WorkerThread 的成员变量,而且 QThread的其他方法也使用到了它,即我们从不同线程访问该成员变量,这时需要自行检查安全性。
void QObject::moveToThread ( QThread * targetThread )
不能认为 monitor 的控制权归属于新线程(在哪里创建就属于哪里)!仅有槽函数在指定线程中调用,包括构造函数都仍然在主线程中调用。
- 若使用默认的
run()
方法或自行调用exec()
,则QThread将开启事件循环。- 以
QThread->start()
开启线程(调用了 QThread 的run()
默认开启事件循环)。
void QThread::run() { (void) exec(); }
- 以
- QThread 提供 exit() 函数和 quit() 槽
- 赋予了QThread使用需要事件循环的非GUI类的能力(QTimer、QTcpSocket 等)。
- 也使得该线程可以关联任意一个线程的信号到指定线程的槽函数。如果一个线程没有开启事件循环,那么该线程中的 timeout() 将永远不会发射。
退出线程过程
在删除 QThread 之前,需要等待 finish
信号 (删除正在运行的 QThread 将导致程序奔溃)。
删除 QThread 对象并不会停止其管理的线程的执行。
-
对于未开启事件循环的线程
仅需让 run() 执行结束即可终止线程:通过 bool 变量进行控制,并定义一个 QMutex 进行加锁保护。 -
对于开启了事件循环的线程:正常的退出线程其实质是退出事件循环
若线程中开始开启了 EvenLoop,耗时代码执行结束后,线程并不会退出。退出方法:
quit()
/exit()
+wait()
terminate()
+wait()
:不推荐(强制结束线程是危险的操作)finished
信号
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
wait(): 等待子线程的结束
Qt - 一文理解QThread多线程(万字剖析整理)
You’re doing it wrong…
实例
- Worker类(耗时程序逻辑处理)
// worker.h
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
~Worker();
public slots:
void doSomething(const QString& cmd);
signals:
void resultNotify(const QString& des);
};
// worker.cpp
Worker::Worker(QObject *parent)
: QObject(parent)
{
}
Worker::~Worker()
{
}
void Worker::doSomething(const QString &cmd) // 耗时程序
{
qDebug() << "doSomething()" << cmd << "thread:" << QThread::currentThreadId();
emit resultNotify("doSomething ok!");
}
Controller
类: 外部调用通过Controller
中operate
发射信号给worker
,worker
进行耗时程序处理。worker
处理完成后,发射信号,controller
中handleResults
槽函数接收,确认程序是否正常。
// Controller.h
class Controller : public QObject
{
Q_OBJECT
public:
Controller(QObject* parent = nullptr);
~Controller();
public slots:
void handleResults(const QString &des);
signals:
void operate(const QString &cmd);
private:
QThread thread;
};
// Controller.cpp
Controller::Controller(QObject* parent)
: QObject(parent)
{
Worker *worker = new Worker();
worker->moveToThread(&thread); // 移入线程
connect(this, &Controller::operate, worker, &Worker::doSomething);
connect(&thread, &QThread::finished, worker, &QObject::deleteLater);
connect(worker, &Worker::resultNotify, this, &Controller::handleResults);
thread.start();
}
Controller::~Controller()
{
thread.quit();
thread.wait();
}
void Controller::handleResults(const QString &des)
{
qDebug() << "handleResults()" << des << "thread:" << QThread::currentThreadId();
}
- 调用:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
Controller* controller = new Controller(this);
emit controller->operate("copy");
}
https://blog.csdn.net/zyhse/article/details/106313994
QT锁QMutex
QMutexLocker
不能够定义 私有成员变量 和 全局变量,只能够定义局部变量来使用。
使用方法:
(1)先定义一个QMutex类的变量
QMutex m_mutex;
(可以是私有成员变量,也可以是全局变量)
(2) 在定义一个QMutexLocker类的变量(注意:在需要上锁的地方直接定义即可)
QMutexLocker locker(&mutex);
a = 5; //等等需要进行写的操作
使用注意:
(1)如果需要对一个全局变量区域进行保护,那么QMutex定义的变量就得是全局的!
(2)QMutexLocker上锁,解锁的原理:在该局部变量被创建的时候上锁,当所在函数运行完毕后该QMutexLocker局部变量在栈中销毁掉(解锁)。
注意,如果该局部变量在中间被打断,那么QMutexLocker上的锁就不会被解锁掉,因为该函数没有被完整的是执行完。QMutexLocker所创建的局部变量也没有被正确销毁销毁,可能就和QMutexLocker他自己本身的机制不服也就不会解锁。
https://www.cnblogs.com/xiangtingshen/p/11078381.html
高级接口
标准库中的future、promise,Qt中的QFuture、QFutureWatcher等。
单例模式(线程安全)
饿汉式单例
class Singleton
{
private:
Singleton(); // private类型的构造函数,外部(其他类对象)不能直接new一个该对象的实例
public:
static bool GetSingleton(); // 该类唯一的一个public方法
private:
static Singleton* m_pSingleton = new Singleton(); // 直接初始化一个实例对象
}
懒汉式单例
class Singleton
{
private:
Singleton();
public:
static Singleton GetInstance();
private:
static Singleton* m_pInstance;
}
Singleton GetInstance()
{
if (instance == NULL)
{
m_pInstance = new Singleton();
}
return m_pInstance
}
在多线程并发下这样的实现是无法保证实例实例唯一的,多个线程可以同时进入getInstance()方法。
解决方案
- 加同步锁 =》 效率低
- 同步代码块 =》 效率低
- 使用静态内置类
class Singleton
{
private:
Singleton();
static class MySingletonHandler{
private static MySingleton instance = new MySingleton();
}
public:
static Singleton GetInstance();
}
Singleton GetInstance()
{
return MySingletonHandler.instance;
}
- 双检查锁机制
Singleton getInstance(){ //对获取实例的方法进行同步
if (instance == null){
synchronized(Singleton.class){
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
https://blog.csdn.net/cselmu9/article/details/51366946
QT获取线程ID(仅调试使用)
QT获取线程号函数currentThreadId()返回Qt::HANDLE 如何得到QString?
(int)currentThreadId()
问题、Warning
- 在加锁状态下无法停止线程,需先解锁。