目录
QThread类提供不依赖于平台的管理线程的方法。一个QThread类的对象管理一个线程,一 般从QThread继承一个自定义类,并重定义虚函数run(),在run()函数里实现线程需要完成的任务。
1、信号与槽的连接方式
信号与槽的连接函数的原型为:
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相同,同样能自动确定连接类型,但是加了限制:同一个信号和同一个槽函数之间只能有一个连接。
2、多线程信号与槽
自定义信号与槽。MyClass类关联ThreadTest类信号函数,ThreadTest类实现线程创建,run()函数中发出信号,类内接收。示例1下面代码中:
1)ThreadTest线程类,通过run()函数不断产生数据,产生后通过自身定义的
信号:counter_reset()、counter(m_counter)发送信号;
槽函数:getCounter(int n)、on_counter_reset()接收信号并显示。
2)MyClass类,定义槽函数getstarted()、getfinished()接收ThreadTest线程类的started()和finished()信号。
// 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();
}
运行结果:
通过线程类中run()函数产生数据,使用信号与槽发送结果,主线程处理类中接收信号,显示结果。信号与槽机制实现了线程类向主线程类数据的主动通知。
3、信号与槽的调用线程?
一个应用程序进程中会有一个主线程,一个进程中可以有多个线程,线程拥有独立的栈空间,而栈空间专门用于函数调用(比如保存函数参数,局部变量等)。
如果一个函数的函数体中没有访问临界资源的代码,那么这个函数可以被多个线程同时调用,不会产生任何副作用。因为线程调用函数的时候用的是自己的栈空间,既然线程的栈空间是独立的,那么谁调用函数就用谁的栈空间,互不干扰。
所以问题来了,槽函数本质也是个函数,那么槽函数是谁调用的呢?是主线程还是定义槽函数的线程还是其他线程?
一般来说,我们开启一个线程,那么这个线程应该会有自己的线程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线程中,后者位于子线程中。下面解决。
4、调整信号与槽所在线程的依附关系
上面测试表明,即使槽函数是定义在线程类中,调用函数的却不一定是这个线程。当然这不是我们希望的,有什么办法让调用槽函数的线程是本线程吗?
在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("myClass");
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() ,这样才能正常使用信号与槽。
结论:
即使槽函数是定义在线程类中,调用函数的却不一定是这个线程。默认情况下,对象依附于自身被创建的线程。对象的依附性确实可以使用moveToThread函数改变。
为什么要改变对象的依附性?改变依附性有什么意义吗?
前面说到多线程的时候强调过,只要用到了多线程,就可能产生临界资源竞争的情况。如果信号的发送和对应槽函数的执行在不同线程时,可能产生临界资源的竞争。
传送门:qt多线程系列文章目录