Qt多线程编程中的对象线程与函数执行线程
近来用Qt编写一段多线程的TcpSocket通信程序,被其中Qt中报的几个warning搞晕了,一会儿是说“Cannot create children for a parent that is in a different thread”,有时候又是“QSocketNotifier: socket notifiers cannot be enabled from another thread”,还经常又Assert failure:Cannot send events toobjects owned by a different thread,从而导致程序崩溃。
为彻底搞清原因并解决问题,在查阅大量资料和Qt文档之后,理清了其中的机制,也对多线程编程中的QObject对象创建以及connect执行有更清楚的认识:
1. 一个对象的线程就是创建该对象时的线程,而不论该对象的定义是保存在那个线程中;
2. QObject的connect函数有几种连接方式,
a) DirectConnection,信号发送后槽函数立即执行,由sender的所在线程执行;
b) QueuedConnection,信号发送后返回,相关槽函数由receiver所在的线程在返回到事件循环后执行;
c) 默认使用的是Qt::AutoConnection,当sender和receiver在同一个线程内时,采用DirectConnection的方式,当sender和receiver在不同的线程时,采用QueuedConnection的方式。
为了更清楚的理解这些问题,在此特编了个小例子说明一下。首先定义一个从QObject继承的类SomeObject,包含一个信号someSignal和一个成员函数callEmitSignal,此函数用于发送前面的someSignal信号。定义如下:
class SomeObject : public QObject
{
Q_OBJECT
public:
SomeObject(QObject* parent=0) : QObject(parent) {}
void callEmitSignal() // 用于发送信号的函数
{
emit someSignal();
}
signals:
void someSignal();
};
然后再定义一个从QThread继承的线程类SubThread,它包含一个SomeObject的对象指针obj,另外有一个slot函数someSolt,定义如下:
class SubThread : public QThread
{
Q_OBJECT
public:
SubThread(QObject* parent=0) : QThread(parent){}
virtual ~SubThread()
{
if (obj!=NULL) delete obj;
}
public slots:
// slot function connected to obj's someSignal
void someSlot();
public:
SomeObject * obj;
};
// slot function connected to obj's someSignal
void SubThread::someSlot()
{
QString msg;
msg.append(this->metaObject()->className());
msg.append("::obj's thread is ");
if (obj->thread() == qApp->thread())
{
msg.append("MAIN thread;");
}
else if (obj->thread() == this)
{
msg.append("SUB thread;");
}
else
{
msg.append("OTHER thread;");
}
msg.append(" someSlot executed in ");
if (QThread::currentThread() == qApp->thread())
{
msg.append("MAIN thread;");
}
else if (QThread::currentThread() == this)
{
msg.append("SUB thread;");
}
else
{
msg.append("OTHER thread;");
}
qDebug() << msg;
quit();
}
这里someSlot函数主要输出了obj所在的线程和slot函数执行线程。
接着从SubThread又继承了3个线程类,分别是SubThread1, SubThread2, SubThread3.分别实现线程的run函数。定义如下:
// define sub thread class 1
class SubThread1 : public SubThread
{
Q_OBJECT
public:
SubThread1(QObject* parent=0);
// reimplement run
void run();
};
class SubThread2 : public SubThread
{
Q_OBJECT
public:
SubThread2(QObject* parent=0);
// reimplement run
void run();
};
class SubThread3 : public SubThread
{
Q_OBJECT
public:
SubThread3(QObject* parent=0);
// reimplement run
void run();
};
在主程序中分别创建3个不同的线程并运行,查看运行结果。
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
SubThread1* t1 = new SubThread1(&a); //由主线程创建
t1->start();
SubThread2* t2 = new SubThread2(&a); //由主线程创建
t2->start();
SubThread3* t3 = new SubThread3(&a); //由主线程创建
t3->start();
return a.exec();
}
下面我们来分析不同写法的程序,其obj对象所在的线程空间和someSlot函数执行的线程空间分别是怎样的。
首先看SubThread1的实现:
////////////////////////////////////////////////////////
// class SubThread1
////////////////////////////////////////////////////////
SubThread1::SubThread1(QObject* parent)
: SubThread(parent)
{
obj = new SomeObject();//由主线程创建
connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()));
}
// reimplement run
void SubThread1::run()
{
obj->callEmitSignal();
exec();
}
可以看到,obj是在构造函数中被创建的,那么创建obj对象的线程也就是创建SubThread1的线程,一般是主线程,而不是SubThread1所代表的线程。同时由于obj和this(即t1)都位于主线程,所以someSlot函数也是由主线程来执行的。
而在线程SubThread2中,我们把obj对象的创建放到子线程的run函数中,那么obj对象的线程就应该SubThread2代表的线程,即t2,就不再是主线程了。
////////////////////////////////////////////////////////
// class SubThread2
////////////////////////////////////////////////////////
SubThread2::SubThread2(QObject* parent)
: SubThread(parent)
{
obj=0;
}
// reimplement run
void SubThread2::run()
{
obj = new SomeObject(); //由当前子线程创建
connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()));
obj->callEmitSignal();
exec();
}
同时,在connect函数中由于obj和this(这里是t2)不是在同一个线程中,因此会采用QueuedConnection的方式,其slot函数由this对象所在的线程即主线程来执行。这里有一个特别容易误解的地方,就是这个slot函数虽然是子线程SubThread2的一个成员函数,connect操作也是在子线程内完成的,但是该函数的执行却不在子线程内,而是在主线程内。
那么如果想让相应的slot函数在子线程内执行,该如何做呢?在子线程的run函数中创建obj对象的同时,在执行connect时指定连接方式为DirectConnection,这样就可以使slot函数在子线程中运行,因为DirectConnection的方式始终由sender对象的线程执行。如
////////////////////////////////////////////////////////
// class SubThread3
////////////////////////////////////////////////////////
SubThread3::SubThread3(QObject* parent)
: SubThread(parent)
{
obj=0;
}
// reimplement run
void SubThread3::run()
{
obj = new SomeObject();
connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()),
Qt::DirectConnection);
obj->callEmitSignal();
exec();
}
最后,该程序的运行结果应该是:
"SubThread1::obj's thread is MAIN thread; someSlot executed in MAIN thread;"
"SubThread2::obj's thread is SUB thread; someSlot executed in MAIN thread;"
"SubThread3::obj's thread is SUB thread; someSlot executed in SUB thread;"
Qt多线程编程中的对象线程与函数执行线程的更多相关文章
- Java多线程编程中Future模式的详解
Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...
- Java多线程编程中Future模式的详解<转>
Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...
- 关于python多线程编程中join()和setDaemon()的一点儿探究
关于python多线程编程中join()和setDaemon()的用法,这两天我看网上的资料看得头晕脑涨也没看懂,干脆就做一个实验来看看吧. 首先是编写实验的基础代码,创建一个名为MyThread的 ...
- C++ 关于MFC多线程编程中的一些注意事项 及自定义消息的处理
在多线程编程中,最简单的方法,无非就是利用 AfxBeginThread 来创建一个工作线程,看一下这个函数的说明: CWinThread* AFXAPI AfxBeginThread( AFX_T ...
- Java多线程编程(2)--多线程编程中的挑战
一.串行.并发和并行 为了更清楚地解释这三个概念,我们来举一个例子.假设我们有A.B.C三项工作要做,那么我们有以下三种方式来完成这些工作: 第一种方式,先开始做工作A,完成之后再开始做工作B ...
- C#多线程编程中的锁系统
C#多线程编程中的锁系统(二) 上章主要讲排他锁的直接使用方式.但实际当中全部都用锁又太浪费了,或者排他锁粒度太大了. 这一次我们说说升级锁和原子操作. 目录 1:volatile 2: Inter ...
- 【C/C++开发】多线程编程中的join函数
多线程编程中的join函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # coding: utf-8 # 测试多线程中join的 ...
- [Vue]vue中各选项及钩子函数执行顺序
在vue中,实例选项和钩子函数和{{}}表达式都是不需要手动调用就可以直接执行的. 一.生命周期图示 二.vue中各选项及钩子函数执行顺序 1.在页面首次加载执行顺序有如下: beforeCreate ...
- Qt多线程编程总结(一)(所有GUI对象都是线程不安全的)
Qt对线程提供了支持,基本形式有独立于平台的线程类.线程安全方式的事件传递和一个全局Qt库互斥量允许你可以从不同的线程调用Qt方法. 这个文档是提供给那些对多线程编程有丰富的知识和经验的听众的.推荐阅 ...
随机推荐
- C# 单例模式的多种简单实现
什么是单例模式? 这里我就不做过多的解释了, 毕竟关于Singleton的资料实在是太多太多了.点击这里 1.简单的思路就是, 创建对象单例的动作转移到另外的行为上面, 利用一个行为去创建对象自身, ...
- hdu6446 Tree and Permutation
没啥好说的,拆一下贡献就完事了.记dis(x,y)为树上x到y的最短路径,设长度为n的排列中有f(n)个里面x和y相邻(不考虑x和y的顺序),那么f(n)=(n-2)! (n-1) 2,显然这个f(n ...
- 【模考】2018.04.08 Travel
Description 有N个人出去旅行,第i个人去A国有Ai种游玩方式,去B国有Bi种游玩方式,问至少有C个人去A国的情况下,所有人的游玩方式有多少种不同的可能. 两种所有人的游玩方式不同当且仅当存 ...
- debian修改时区
这里提供两种方法 一.使用dpkg-reconfigure修改 # dpkg-reconfigure tzdata Current default time zone: 'Asia/Shanghai' ...
- java类加载详解
1,类的加载过程: JVM将类加载过程分为三个步骤:装载(load),链接(link)和初始化(initialize),其中链接又分为三个步骤: 验证(varification),准备(Prepara ...
- L. Twice Equation ACM Nanning 2017
https://nanti.jisuanke.com/t/19978 acm提交:类 Main 使用java:高精度 BigInteger import java.math.BigInteger; i ...
- OpenJudge1001Exponentiation
问题描述 Problems involving the computation of exact values of very large magnitude and precision are co ...
- LigerUI下拉选择列表LigerComboBox中tree的节点初始化默认选中的问题
问题描述 用后台的Json传送tree的数据 前端用js方法来控制选中节点 此方法存在bug 实例: bug问题:无论设置的默认选中值是多少个,前台checkbox最多只显示选中一个,但是内容框中显示 ...
- css--display属性中inline-block与inline的区别
inline-block 与 inline 的区别: inline-block 与inline 效果类似,但是inline-block是可以设定宽度和高度的!!而行内元素(也就是inline)是无法设 ...
- 第10章-Vue.js 项目实战
一.本节内容 掌握项目环境中路由的配置方法 ***** 熟练掌握编写单文件组件的编写 *** 能够使用swiper.js进行轮播图组件的封装 能够使用axios进行数据请求 二.webpack项目的目 ...