目录
1、QThread类
2、创建并启动线程
3、多线程信号与槽
4、信号与槽的调用线程?
5、调整信号与槽所在线程的依附关系
6、信号与槽的连接方式
QT 中 QObject 作QT中类的最终父类,具有自定义信号与槽的能力,只要继承自这个类的类,也一样拥有自定义信号和槽的能力。QT 中定义信号与槽是十分有用的,QT 下多线程类QThread 是继承自 QObject,同样具有有自定义信号和槽的能力。
1、QThread类
QThread类提供不依赖于平台的管理线程的方法。一个QThread类的对象管理一个线程,一 般从QThread继承一个自定义类,并重定义虚函数run(),在run()函数里实现线程需要完成的任务。
将应用程序的线程称为主线程,额外创建的线程称为工作线程。一般在主线程里创建工作线程,并调用start()开始执行工作线程的任务。start()会在内部调用run()函数,进入工作线程的事件循环,在run()函数里调用exit()或quit()可以结束线程的事件循环,或在主线程里调用terminate() 强制结束线程。
QThread类的主要接口函数、信号和槽函数见下表:
QThread是QObject的子类,所以可以使用信号与槽机制。QThread自身定义了started()和finished()两个信号,started()信号在线程开始执行之前发射,也就是在run()函数被调用之前,finished()信号在线程就要结束时发射。
2、创建并启动线程
执行步骤:
1)自定义类继承自QThread;
2)实现run()函数;
3)主函数中定义类对象并调用start()。
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
class ThreadTest: public QThread
{
public:
ThreadTest() {}
void run()
{
qDebug()<<objectName()<<" : run";
sleep(2);
qDebug()<<objectName()<<" : exit";
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ThreadTest threada;
threada.setObjectName(ThreadTest);
threada.start();
qDebug()<<"threada end";
return a.exec();
}
结果:
3、多线程信号与槽
自定义信号与槽。MyClass类关联ThreadTest类信号函数,ThreadTest类实现线程创建,run()函数中发出信号,类内接收。
// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QObject>
#include <QDebug>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(){}
public slots:
void getstarted()
{
qDebug()<<objectName()<<" : getstarted();"
}
void getfinished()
{
qDebug()<<objectName()<" : getfinished()";
}
};
#endif // MYCLASS_H
// threadtest.h
#ifndef THREADTEST_H
#define THREADTEST_H
#include <QThread>
#include <QDebug>
class ThreadTest: public QThread
{
Q_OBJECT
public:
ThreadTest()
{
connect(this, SIGNAL(counter(int)), this, SLOT(getCounter(int)));
connect(this, SIGNAL(counter_reset()), this, SLOT(on_counter_reset()));
}
void run()
{
int m_counter = 0;
for(int i=0; i<10; i++)
{
if(0==i%5)
{
m_counter = 0;
emit counter_reset();
}
emit counter(m_counter);
m_counter = m_counter + 1;
msleep(200);
}
}
signals:
void counter(int n);
void counter_reset();
public slots:
void getCounter(int n)
{
qDebug()<< "getCounter : "<<n;
}
void on_counter_reset()
{
qDebug()<<" counter reset now !";
}
};
#endif // THREADTEST_H
main.cpp函数调用:
#include <QCoreApplication>
#include <QObject>
#include <QDebug>
#include <QThread>
#include myclass.h
#include threadtest.h
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ThreadTest threada;
MyClass my;
my.setObjectName(my);
QObject::connect(&threada, SIGNAL(started()), &my, SLOT(getstarted()));
QObject::connect(&threada, SIGNAL(finished()), &my, SLOT(getfinished()));
threada.start();
return a.exec();
}
运行结果:
4、信号与槽的调用线程?
一个应用程序进程中会有一个主线程,一个进程中可以有多个线程,线程拥有独立的栈空间,而栈空间专门用于函数调用(比如保存函数参数,局部变量等)。
如果一个函数的函数体中没有访问临界资源的代码,那么这个函数可以被多个线程同时调用,不会产生任何副作用。因为线程调用函数的时候用的是自己的栈空间,既然线程的栈空间是独立的,那么谁调用函数就用谁的栈空间,互不干扰。
所以问题来了,槽函数本质也是个函数,那么槽函数是谁调用的啊?是主线程还是定义槽函数的线程还是其他线程?
一般来说,我们开启一个线程,那么这个线程应该会有自己的线程id。为了知道当前线程的id,QThread提供currentThreadId()方法返回当前的线程ID,函数声明如下:
static Qt::HANDLE currentThreadId() Q_DECL_NOTHROW Q_DECL_PURE_FUNCTION;
添加线程ID打印,测试:
// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QObject>
#include <QDebug>
#include <QThread>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(){}
public slots:
void getstarted()
{
qDebug()<<objectName()<<":"<<"getstarted() , tid :"<<QThread::currentThreadId();
}
void getfinished()
{
qDebug()<<objectName()<<":"<<"getfinished(), tid :"<<QThread::currentThreadId();
}
};
#endif // MYCLASS_H
// threadtest.h
#ifndef THREADTEST_H
#define THREADTEST_H
#include <QThread>
#include <QDebug>
class ThreadTest: public QThread
{
Q_OBJECT
public:
ThreadTest()
{
connect(this, SIGNAL(counter(int)), this, SLOT(getCounter(int)));
connect(this, SIGNAL(counter_reset()), this, SLOT(on_counter_reset()));
}
void run()
{
qDebug()<<"ThreadTest tid "<<currentThreadId();
int m_counter = 0;
for(int i=0; i<10; i++)
{
if(0==i%5)
{
m_counter = 0;
emit counter_reset();
}
emit counter(m_counter);
m_counter = m_counter + 1;
msleep(200);
}
}
signals:
void counter(int n);
void counter_reset();
public slots:
void getCounter(int n)
{
qDebug()<< " tid:"<<currentThreadId()<< ", getCounter : " <<n;
}
void on_counter_reset()
{
qDebug()<<" tid:"<<currentThreadId()<< "counter reset now !";
}
};
#endif // THREADTEST_H
main.cpp函数调用:
#include <QCoreApplication>
#include <QObject>
#include <QDebug>
#include <QThread>
#include myclass.h
#include threadtest.h
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"main tid:"<< QThread::currentThreadId();
ThreadTest threada;
MyClass my;
my.setObjectName(my);
QObject::connect(&threada,SIGNAL(started()),&my,SLOT(getstarted()));
QObject::connect(&threada,SIGNAL(finished()),&my,SLOT(getfinished()));
threada.start(); //开启线程执行函数
qDebug()<<"main end";
return a.exec();
}
运行结果:
从运行结果看,我们的槽函数好像并不是在我们开启的线程中调用的呢,而是在主线程中。槽函数在线程类中写的,却跑到主线程中调用了,这也太奇怪了???
问题:槽函数执行时的所在线程和信号发送操作的所在线程并不是同一个,前者位于main线程中,后者位于子线程中。下面解决。
5、调整信号与槽所在线程的依附关系
上面测试表明,即使槽函数是定义在线程类中,调用函数的却不一定是这个线程。当然这不是我们希望的,有什么办法让调用槽函数的线程是本线程吗?
在QT中我们应该要知道几个问题:
1)对象依附于哪个线程;
2)对象的依附性与槽函数执行的关系;
3)对象的依附性是否可以改变,如何改变。
默认情况下,对象依附于自身被创建的线程。从代码中发现,是主线程创建了ThreadTest threada; 和 MyClass my;这两个对象,那么这两个对象就依附于主线程了。
默认情况下,槽函数在对象所依附的线程中执行。所以就出现了上面情况。
对象的依附性确实可以改变,从源码中我们可以找到这样一个函数:
//在 QObject 中
void moveToThread(QThread *thread); // 该方法定义在了 QObject 中,通过这个方法可以改变对象的依附性。
测试代码(最终版):
在main函数中添加:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<main tid:<< QThread::currentThreadId();
ThreadTest threada;
MyClass my;
my.setObjectName(my);
QObject::connect(&threada,SIGNAL(started()),&my,SLOT(getstarted()));
QObject::connect(&threada,SIGNAL(finished()),&my,SLOT(getfinished()));
// 改变对象依附
threada.moveToThread(&threada);
my.moveToThread(&threada);
threada.start();
qDebug()<<main end;
return a.exec();
}
结果:
我们发送信号后,信号进入了事件队列里啦。但是只是进入到事件队列却不去处理的话并没什么用,这就好比顾客点了菜,却没有人告诉厨师要做什么菜。调用exec();开启事件循环后就可以处理消息队列的消息了。
信号与槽需要事件循环来支持,因为Qt4.4之后run()默认调用 QThread::exec() ,开启了事件循环,所以我们不主动在子线程中调用 exec()也能正常使用信号与槽。在4.4之前(包括4.4)我们需要自己手动开启事件循环,也就是调用QThread::exec() ,这样才能正常使用信号与槽。
为什么要改变对象的依附性?改变依附性有什么意义吗?
前面说到多线程的时候强调过,只要用到了多线程,就可能产生临界资源竞争的情况。如果信号的发送和对应槽函数的执行在不同线程时,可能产生临界资源的竞争。
6、信号与槽的连接方式
信号与槽的连接函数的原型为:
bool QObject::connect (const QObject * sender,
const char * signal,
const QObject * receiver,
const char * method,
Qt::ConnectionType type = Qt::AutoConnection)
其中第5个参数决定信号与槽的连接方式,用于决定槽函数被调用时的相关行为。
Qt::AutoConnection 默认连接
Qt::DirectConnection 槽函数立即调用
Qt::BlockingQueuedConnection 同步调用
Qt::QueuedConnection 异步调用
Qt::UniqueConnection 单一连接
1) Qt::DirectConnection(立即调用)
直接在发送信号的线程中调用槽函数(发送信号和槽函数位于同一线程),等价于槽函数的实时调用。
2) Qt::QueuedConnection(异步调用)
信号发送至目标线程的事件队列(发送信号和槽函数位于不同线程),交由目标线程处理,当前线程继续向下执行。
3) Qt::BlockingQueuedConnection(同步调用)
信号发送至目标线程的事件队列,由牧宝想线程处理。当前线程阻塞等待槽函数的返回,之后向下执行。
4) Qt::AutoConnection(默认连接)
当发送信号线程=槽函数线程时,效果等价于Qt::DirectConnection;
当发送信号线程!=槽函数线程时,效果等价于Qt::QueuedConnection。
Qt::AutoConnection是connect()函数第5个参数的默认值,也是实际开发中字常用的连接方式。
5) Qt::UniqueConnection(单一连接)
功能和AutoConnection相同,同样能自动确定连接类型,但是加了限制:同一个信号和同一个槽函数之间只能有一个连接。
补充阅读:
Qt多线程中的信号与槽_echo_bright_的博客-CSDN博客_qt多线程信号与槽
QT中的多线程编程:
QT中的多线程编程_洋葱汪的博客-CSDN博客