Qt 多线程注意事项
Qt中使用多线程的注意事项:
- 默认的线程在Qt中称之为窗口线程,也叫主线程,负责窗口事件处理或者窗口控件数据的更新
- 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理
- 主线程和子线程之间如果要进行数据的传递,需要使用Qt中的信号槽机制
QThread
常用函数
// 判断线程中的任务是不是处理完成
bool QThread::isFinished() const;
// 判断子线程是否运行
bool QThread::isRunning() const;
// 当前线程的优先级
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);
// 优先级:
// QThread::IdlePriority --> 最低的优先级
// QThread::LowestPriority
// QThread::LowPriority
// QThread::NormalPriority
// QThread::HighPriority
// QThread::HighestPriority
// QThread::TimeCriticalPriority --> 最高的优先级
// QThread::InheritPriority --> 子线程和其父线程的优先级相同, 默认是这个
// 退出线程函数
void QThread::exit(int returnCode = 0);
// 调用线程退出函数之后, 线程不会马上退出;因为当前任务有可能还没有完成, 调回用这个函数是等待任务完成, 然后退出线程, 一般情况下会在 exit() 后边调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);
退出线程
QThread的函数在运行完run函数后就视为线程完成,会发射finish信号。
子线程指针,尽量不要去delete ,这样不安全。一般会绑定QObject::deleteLater()方法。
connect(pThread,&QThread::finished ,thread,&QObject::deleteLater);
正常的退出线程其实质是退出事件循环(实际运用中是执行完run()函数),即执行exit(int returnCode = 0)函数。返回0代表成功,其他非零值代表异常。quit() 函数等价于 exit(0)。线程退出后会发出 finished() 信号。
SIGNAL、SLOT
// 和调用 exit() 效果是一样的
// 调用这个函数之后, 再调用 wait() 函数
[slot] void QThread::quit();
// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);
// 线程退出, 可能是会马上终止线程, 一般情况下不使用这个函数(可能会出错)
[slot] void QThread::terminate();
// 线程中执行的任务完成了, 发出该信号
// 任务函数中的处理逻辑执行完
[signal] void QThread::finished();
// 开始工作之前发出这个信号, 一般不使用
[signal] void QThread::started();
常用静态函数
// 返回当前执行线程的QThread的指针
[static] QThread *QThread::currentThread();
// 返回可以在系统上运行的理想线程数 和当前电脑的 CPU 核心数相同
[static] int QThread::idealThreadCount();
// 休眠函数
[static] void QThread::msleep(unsigned long msecs); // 单位: 毫秒
[static] void QThread::sleep(unsigned long secs); // 单位: 秒
[static] void QThread::usleep(unsigned long usecs); // 单位: 微秒
使用方式一
继承QThread,实现run函数
[virtual protected] void QThread::run();
class myThread:public QThread{
protected:
void run(){
// 实现
}
signals:
void SigFinished(void);// run()函数执行结束后自动触发
}
myThread * subThread = new myThread();
connect(myThread, &myThread::SigFinished, myThread, QThread::deleteLater);//自动释放
subThread->start(); // 启动线程
如果线程中有操作UI或者与其他模块交互的需求;通过信号将信息传递出去。
#include <QThread>
class myThread : public QThread
{
Q_OBJECT
public:
explicit myThread(QObject *parent = nullptr);
protected:
void run(){
qDebug() << "当前线程对象的地址: " << QThread::currentThread();
int num = 0;
while(1)
{
emit curNumber(num++);
if(num == 10000000)
{
break;
}
QThread::usleep(1);
}
qDebug() << "run() 执行结束, 子线程退出...";
}
signals:
// 自定义信号, 传递数据
void curNumber(int num);
public slots:
};
使用方式二
-
从QObject派生一个类
-
类中添加一个公共成员函数(也可以添加多个,在不同的线程中执行),函数就是要在线程中执行的业务
class MyWork:public QObject{
public:
// 函数名称自定义,合法名称都可以
void myWork();
}
- 主线程中创建一个QThread对象,和work对象
QThread * sub = new QThread();
// 如果给woker指定了父对象, 这个函数调用就失败了
MyWork * worker = new MyWork();
// 移动到子线程中工作
worker->moveToThread(sub);
// 连接QThread的started信号,在调用sub->start()开始执行myWork()
QObject::connect(sub, &QThread::started, worker, &MyWork::myWork); // 连接信号和槽函数
sub->start(); // 启动新线程
moveToThread函数的注意事项:
- 只有QObject对象可以使用moveToThread函数,其他对象不能使用。
- 一旦调用了moveToThread函数,这个对象的线程上下文就会改变,因此在调用该函数之后,这个对象所属的线程上下文不能再使用。
- 如果对象正在执行某个函数,而该函数又没有使用线程锁,那么在移动对象之后,该函数仍然会在原来的线程中执行。因此,在移动对象之前,需要确保该对象不处于执行状态。
- 如果一个QObject对象的子对象也需要移动到新线程中,那么这些子对象也必须调用moveToThread函数进行移动。