在QT中将子类中的运算扔到子线程中有两种方法,一种是将子类继承于QObject,调用QObject::moveToThread()方法,从主线程中发送信号调用子类的槽函数;另一种是将子类继承于QThread,重写run()函数。
子类定义
//.h
class MyChildQObjectThread : public QObject
{
Q_OBJECT
public:
MyChildQObjectThread();
~MyChildQObjectThread();
public slots:
void slot_perform();
signals:
void sig_begin_perform();
void sig_finish_perform();
};
//.cpp
MyChildQObjectThread::MyChildQObjectThread()
{
qDebug()<<"MyChildQObjectThread的构造函数所在线程:"<<QThread::currentThreadId();
connect(this,&MyChildQObjectThread::sig_begin_perform,this,&MyChildQObjectThread::slot_perform);
}
MyChildQObjectThread::~MyChildQObjectThread()
{
qDebug()<<"MyChildQObjectThread的析构函数所在线程:"<<QThread::currentThreadId();
}
void MyChildQObjectThread::slot_perform()
{
qDebug()<<"MyChildQObjectThread的执行函数所在线程:"<<QThread::currentThreadId();
emit sig_finish_perform();
}
应用
//.h
QThread WorkThread;
MyChildThread *pMyChildThread;
//.cpp
pMyChildQObjectThread = new MyChildQObjectThread();
pMyChildQObjectThread->moveToThread(&WorkThread);
connect(&WorkThread,&QThread::finished,pMyChildQObjectThread,&QObject::deleteLater);
connect(pMyChildQObjectThread,&MyChildQObjectThread::sig_finish_perform,this,&MainWindow::slot_receive_perform);
WorkThread.start();
emit pMyChildQObjectThread->sig_begin_perform();
运行结果
MainWindow 所在线程: 0x658
MyChildQObjectThread的构造函数所在线程: 0x658
MyChildQObjectThread的执行函数所在线程: 0x7204
MyChildQObjectThread的析构函数所在线程: 0x7204
总结
//.h
class MyChildThread : public QThread
{
Q_OBJECT
public:
MyChildThread();
~MyChildThread();
protected:
void run() override;
signals:
};
//.cpp
MyChildThread::MyChildThread()
{
qDebug()<<"MyChildThread的构造函数所在线程:"<<QThread::currentThreadId();
}
MyChildThread::~MyChildThread()
{
qDebug()<<"MyChildThread的析构函数所在线程:"<<QThread::currentThreadId();
}
void MyChildThread::run()
{
qDebug()<<"MyChildThread的执行函数所在线程:"<<QThread::currentThreadId();
}
应用
pMyChildThread = new MyChildThread();
connect(pMyChildThread,&QThread::finished,pMyChildThread,&QObject::deleteLater);
pMyChildThread->start();
运行结果
MainWindow 所在线程: 0x6968
MyChildThread的构造函数所在线程: 0x6968
MyChildThread的执行函数所在线程: 0x1694
MyChildThread的析构函数所在线程: 0x6968
总结
以下是QT帮助文档中对线程池的描述
QThreadPool管理和回收单独的QThread对象,以帮助减少使用线程的程序中的线程创建成本。每个Qt应用程序都有一个全局QThreadPool对象,可以通过调用globalInstance()来访问它。
要使用一个QThreadPool线程,子类QRunnable并实现run()虚函数。然后创建该类的一个对象并将其传递给QThreadPool::start()。
从上述描述中,我们可以得知,在程序运行过程中对线程的创建和销毁是需要耗费资源的,因此当在需要频繁创建&销毁子线程的场景中,使用QThreadPool是个不错的选择。
子类定义
//.h
struct MyStruct{
int x;
QString str;
};
class MyThreadPool : public QObject, public QRunnable
{
Q_OBJECT
public:
MyThreadPool(MyStruct temp);
~MyThreadPool();
protected:
void run() override;
private:
MyStruct m_struct;
QElapsedTimer test_timer;
signals:
void sig_output(MyStruct result);
};
//.cpp
MyThreadPool::MyThreadPool(MyStruct temp) : m_struct(temp)
{
qDebug()<<"MyThreadPool的构造函数所在线程:"<<QThread::currentThreadId();
}
MyThreadPool::~MyThreadPool()
{
qDebug()<<"MyThreadPool的析构函数所在线程:"<<QThread::currentThreadId();
}
void MyThreadPool::run()
{
test_timer.restart();
while(test_timer.elapsed() < 100)
;
int id = (int)(QThread::currentThreadId());
m_struct.str.append(tr("第%1次线程ID:0x").arg(m_struct.x));
m_struct.str.append(QString::number(id,16));
emit sig_output(m_struct);
}
应用
{
p_my_threadpool = new MyThreadPool(mystruct);
connect(p_my_threadpool,&MyThreadPool::sig_output,this,&MainWindow::slot_receive_result);
p_my_threadpool->setAutoDelete(m_auto_delete_runnable);
QThreadPool::globalInstance()->start(p_my_threadpool);
}
void MainWindow::slot_receive_result(MyStruct temp)
{
ui->textEdit->append(QString("接收到处理结果:%1\n").arg(temp.x).append(temp.str));
if(temp.x == timer_max_count)
{
ui->textEdit->append(str);
}
}
运行结果
MainWindow 所在线程: 0x2c3c
"MyThreadPool第1次的构造函数所在线程:" 0x2c3c
"MyThreadPool第2次的构造函数所在线程:" 0x2c3c
"MyThreadPool第3次的构造函数所在线程:" 0x2c3c
"MyThreadPool第4次的构造函数所在线程:" 0x2c3c
"MyThreadPool第5次的构造函数所在线程:" 0x2c3c
"MyThreadPool第6次的构造函数所在线程:" 0x2c3c
"MyThreadPool第7次的构造函数所在线程:" 0x2c3c
"MyThreadPool第8次的构造函数所在线程:" 0x2c3c
"MyThreadPool第9次的构造函数所在线程:" 0x2c3c
"MyThreadPool第1次的析构函数所在线程:" 0x6fe0
"MyThreadPool第10次的构造函数所在线程:" 0x2c3c
"MyThreadPool第2次的析构函数所在线程:" 0x4a3c
"MyThreadPool第3次的析构函数所在线程:" 0x1fd8
"MyThreadPool第4次的析构函数所在线程:" 0x59d4
"MyThreadPool第5次的析构函数所在线程:" 0x660c
"MyThreadPool第6次的析构函数所在线程:" 0x54e0
"MyThreadPool第7次的析构函数所在线程:" 0x6b10
"MyThreadPool第8次的析构函数所在线程:" 0x56bc
"MyThreadPool第9次的析构函数所在线程:" 0x445c
"MyThreadPool第10次的析构函数所在线程:" 0x6fe0
定时器1次结束计时
定时器2次结束计时
定时器3次结束计时
定时器4次结束计时
定时器5次结束计时
定时器6次结束计时
定时器7次结束计时
定时器8次结束计时
定时器9次结束计时
定时器10次结束计时
接收到处理结果:1
第1次线程ID:0x6fe0
接收到处理结果:2
第2次线程ID:0x4a3c
接收到处理结果:3
第3次线程ID:0x1fd8
接收到处理结果:4
第4次线程ID:0x59d4
接收到处理结果:5
第5次线程ID:0x660c
接收到处理结果:6
第6次线程ID:0x54e0
接收到处理结果:7
第7次线程ID:0x6b10
接收到处理结果:8
第8次线程ID:0x56bc
接收到处理结果:9
第9次线程ID:0x445c
接收到处理结果:10
第10次线程ID:0x6fe0
总结
2022.12.21日更新
Qt Concurrent类可以将一个函数扔到子线程中去执行,根据Qt文档的说明,它封装了高级API,避免使用互斥锁、读写锁、信号量这些低级线程用语。
我的个人使用示例如下:
QT += concurrent
#include <QtConcurrent>
void MainWindow::example_function(int input_A, int input_B)
{}
void MainWindow::run_Concurrent()
{
int input_x, input_y;
input_x = 1;
input_y = 2;
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
QFuture<void> result = QtConcurrent::run(&MainWindow::example_function,this,input_x,input_y);
#else
QFuture<void> result = QtConcurrent::run(this,&MainWindow::example_function,input_x,input_y);
#endif
result.waitForFinished();
}
2022.12.21日更新完毕
不同线程中进行数据通信主要有两种方式:共享内存和信号槽。
共享内存想起来后补充
信号槽默认可以传输QT自己的元数据类型,如QString、double、int等类型,若想要传输自定义结构体数据,则需要在定义一个结构体后调用Q_DECLARE_METATYPE来向QT注册自定义结构体,如下:
//.h
struct MyStruct{
int x;
QString str;
};
Q_DECLARE_METATYPE(MyStruct)
signals:
void sig_output(MyStruct result);
//.cpp
connect(p_my_threadpool,&MyThreadPool::sig_output,this,&MainWindow::slot_receive_result);
一般来说,我们在调用connect函数时只用了四个参数,即发送者,发送信号,接收者,接收槽函数,但是connect函数还存在了第五个参数:连接方式Qt::ConnectionType
连接类型 | 使用场景 |
---|---|
Qt::AutoConnection | 默认值,如果发送者和接收者在同一个线程,则执行Qt::DirectConnection,否则会执行Qt::QueuedConnection。 |
Qt::DirectConnection | 信号发出后,立即执行槽函数;槽函数在发送者线程执行。 |
Qt::QueuedConnection | 信号发出后,不会立即执行槽函数,而是等到接收者进入事件循环之后,槽函数才会被调用;槽函数在接收者线程执行。 |
Qt::BlockingQueuedConnection | 槽函数执行方式和Qt::QueuedConnection相同,不同之处在于该信号发出后,会阻塞发送者线程,直到槽函数执行完毕返回,因此若发送者和接收者在同一个线程中,将会造成死锁。此方式只能在多线程中运用。 |
Qt::UniqueConnection | 此连接方式在禁止多次连接相同信号和槽的时候启用,因为在信号和槽已经connect情况下再次连接时,connect将会失败。 |
Qt::SingleShotConnection | 此连接方式在只需要连接一次信号槽的时候启用,因为在信号发送一次后,connect将被断开,槽函数只会执行一次。 |
一般情况下,不需要设置第五个参数,QT会根据发送者和接收者所在的线程自行判断是用直接连接还是队列连接。
以上是我在实际运用过程中的一点总结,以备后续查阅。
见识浅薄,以娱大家。
这是本项目的Github地址:ThreadPoolDemo
本项目编译环境:Qt 6.2.4 Community \ MSVC 2019