QThread 与 QObject的关系(QObject可以用于多线程,可以发送信号调用存在于其他线程的slot函数,但GUI类不可重入)
QThread 继承 QObject.。它可以发送started和finished信号,也提供了一些slot函数。
QObject.可以用于多线程,可以发送信号调用存在于其他线程的slot函数,也可以postevent给其他线程中的对象。之所以可以这样做,是因为每个线程都有自己的事件循环。
在进行下面的讲解之前,应该了解的重要的一点是:QThread 对象所在的线程,和QThread 创建的线程,也就是run()函数执行的线程不是同一个线程。QThread 对象所在的线程,就是创建对象的线程。我们通过一个例子说明更能清楚一点:
MyThread::MyThread(QObject *parent /* = NULL */):QThread(parent)
{
qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThreadId();
}
void MyThread::run()
{
qDebug()<<"run() currentThreadId : "<<QThread::currentThreadId();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyThread thread;
qDebug()<<"mainThread : "<<QThread::currentThreadId();
thread.start();
returna.exec();
}
输出结果:MyThread所在的线程就是主线程,run()函数是新开的线程。
QObject Reentrancy
QObject.是可重入的,它的大多数非GUI子类,例如QTimer, QTcpSocket, QUdpSocket and QProcess都是可重入的,使得这些类可以同时用于多线程。需要注意的是,这些类设计为从一个单一的线程创建和使用的,在一个线程创建对象,而从另外一个线程调用对象的函数并不能保证行得通。有三个限制需要注意:
1. QObject的子对象必须在创建其parent的线程中创建。这意味着,你不能把QThread对象作为parent传递给创建在线程中的对象,因为QThread 对象本身在另外一个线程中创建。
2. 事件驱动对象只能用于单线程。尤其是在定时器机制和网络模块。例如,你不能在不是对象所处的线程start一个计时器或者链接一个secket。简单的说就是,你不能在线程A创建了一个计时器timer,然后在线程B从启动timer。
我们可以验证一下:
class MyThread : publicQThread
{
Q_OBJECT
public:
MyThread(QObject *parent = NULL);
~MyThread();
public slots:
voidtimeOutSlot();
protected:
voidrun();
QTimer *m_pTimer;
};
MyThread::MyThread(QObject*parent /* = NULL */):QThread(parent)
{
m_pTimer = newQTimer(this);
qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThreadId();
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
}
void MyThread::timeOutSlot()
{
qDebug()<<"timer timeout ";
}
MyThread::~MyThread()
{
}
void MyThread::run()
{
m_pTimer->start(500);
qDebug()<<"run() currentThreadId : "<<QThread::currentThreadId();
qDebug( "finish!");
}
intmain(int argc, char*argv[])
{
QApplication a(argc, argv);
MyThread thread;
qDebug()<<"mainThread : "<<QThread::currentThreadId();
thread.start();
returna.exec();
}
Timeout函数并没有被调用。我们还发现有多了一行输出:QObject::startTimer: timers cannot be startedfrom another thread
跟踪timer的start源码,我们发现:
void QEventDispatcherWin32::registerTimer(int timerId, intinterval, QObject *object)
{
if (timerId< 1 || interval < 0 || !object) {
qWarning("QEventDispatcherWin32::registerTimer:invalid arguments");
return;
}
else if(object->thread() != thread() || thread() != QThread::currentThread())
{
//判断object的thread,也就是object所在的thread,不等于当前的线程就返回了
qWarning("QObject::startTimer:timers cannot be started from another thread");
return;
}
。。。。。
}
3. 你必须保证在线程中创建的对象要在线程销毁前delete。这很容易做到,只要是在run()函数栈里创建的对象就行。
尽管 QObject 是可重入的,但是GUI类,特别是QWidget 和它的子类都是不可重入的。它们只能在主线程中用。就如前面提到的, QCoreApplication::exec()必须从主线程进行调用。
Per-Thread Event Loop
每个线程都有自己的事件循环。起始的线程用QCoreApplication::exec()开启事件循环。其他的线程用QThread::exec()开始事件循环。与 QCoreApplication一样, QThread也提供了 exit(int) 函数 和 quit() 槽函数。
线程里的事件循环,使得可以在线程里使用需要事件循环的非GUI类,例如(QTimer, QTcpSocket, and QProcess).。也可以把任意的线程的信号连接到特定线程的槽。
QObject实例存在于创建实例的线程中,发送给实例事件也是有线程的事件循环实现的。可以用 QObject::thread().获取对象存活于哪个线程。
MyThread::MyThread(QObject*parent /* = NULL */):QThread(parent)
{
m_pTimer = newQTimer(this);
qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThread();
QObject obj1;
obj1.thread();
qDebug()<<"obj1live in the thread :"<<obj1.thread();
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
//QThread::start();
}
void MyThread::run()
{
QObject obj2;
obj2.thread();
qDebug()<<"button2live in the thread :"<<obj2.thread();
//m_pTimer->start(500);
qDebug()<<"run() currentThreadId : "<<QThread::currentThread();
qDebug( "finish!");
}
这个再一次说明了,对象所处的线程就是创建它的线程。
注意:对于那些在QApplication之前创建的对象,QObject::thread() 返回0。这意味着,主线程只处理发送给那些对象的事件,那些没有thread的对象是不做任何的事件处理的。使用QObject::moveToThread()函数可以改变对象及其子对象的线程关联度,说白了就是把对象从当前的线程移到另外的线程里。但是如果一个对象已经有了parent,那是不能move了。
调用delete删除处于另外一个线程的QObject对象是不安全的。除非你能保证对象当前不是在进行事件处理。应该用QObject::deleteLater()替代,并且将发出一个DeferredDelete事件,这个事件会最终会被对象的线程的时间循环所捕获。
如果没有时间循环,就不会有事件传递给对象。例如,如果你在一个线程中创建了一个QTimer对象,但是不调用exec(),,那么QTimer永远不会发出timeout()信号,调用eleteLater() 也不起作用。
void MyThread::run()
{
m_pTimer = newQTimer();
m_pTimer->start(500);
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
qDebug()<<"run() currentThreadId : "<<QThread::currentThread();
this->exec();
//qDebug("finish!" );
}
void MyThread::timeOutSlot()
{
qDebug()<<"timer timeout ";
//m_pTimer->stop();
}
这时候是可以调用timeOutSlot()的。
void MyThread::run()
{
m_pTimer = newQTimer();
m_pTimer->start(500);
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
qDebug()<<"run() currentThreadId : "<<QThread::currentThread();
//this->exec();
//qDebug("finish!" );
}
如果注释//this->exec();,timeOutSlot()将不会被调用。
还有一点要注意的:QTimer对象也不能在另外的线程stop的。如果把timeOutSlot里的m_pTimer->stop();取消注释。会看到一行输出:QObject::killTimer: timers cannot be stopped fromanother thread
源码中:
bool QEventDispatcherWin32::unregisterTimer(int timerId)
{
if (timerId< 1) {
qWarning("QEventDispatcherWin32::unregisterTimer:invalid argument");
return false;
}
QThread *currentThread = QThread::currentThread();
//判断timer所处的线程与当前的线程是否一致。
if(thread() != currentThread)
{
qWarning("QObject::killTimer:timers cannot be stopped from another thread");
return false;
}
。。。。
}
你可以用QCoreApplication::postEvent()函数在任意时间给任意线程中的任意对象发送事件。事件自动被创建object的线程的事件循环分发。所以的线程都支持事件过滤器,唯一的限制就是,监视对象必须与被监视对象处于同一个线程。同样的,QCoreApplication::sendEvent() 只能用来给与调用QCoreApplication::sendEvent() 函数处于同一个线程的对象发送事件。说白了就是,QCoreApplication::sendEvent() 不能给处于另外线程的对象发送事件。
Accessing QObjectSubclasses from Other Threads
QObject 和它所有的子类都不是线程安全的。这包含了整个事件发送系统,需要记住的很重要的一点是:事件循环可能正在给一个对象发送一个事件,同时你可能从别的线程访问该对象。
如果你调用了一个不是出于当前线程QObject 子类对象的一个函数,而此时对象可能接收一个事件,你必须用一个mutex保护对象的内在的数据。否则,可能引起程序崩溃或者未定义的行为。
与其他的对象一样,QThread对象存活于创建对象的线程中,而不是存在于QThread::run() 线程。这点在前面讲到了。在自定义 QThread子类中提供slot函数是不安全的,除非你用一个mutex保护了成员变量。然而,你可以在实现的 QThread::run() 里发出信号,因为信号发送是线程安全的。
Signals and Slots AcrossThreads
Qt支持了几种信号--槽的连接方式:
1. Auto Connection (默认):如果如果信号的发送方与接收方是处于同一个线程,这个连接就是 Direct Connection,否则就跟 Queued Connection一样。
2. Direct Connection :当信号发出之后,槽会立即被调用。槽函数是在信号发送方的线程中运行的,不需要接收方的线程。
3. Queued Connection:当控制权回到接收方线程时调用槽函数。槽函数是在接收方的线程中运行的。
4. Blocking Queued Connection :调用方式跟 Queued Connection一样,区别在于,当前线程会被阻塞直到槽函数返回。
5. Unique Connection :这种方式跟 Auto Connection一样,但是只有当不存在一个相同的连接时才会创建一个连接。如果已经存在相同的连接,则不会创建连接,connect()返回false。
可以在connect()添加参数指定连接类型。需要注意的一点是:如果信号发送方和接收方处于不同的线程,而且接收方线程运行着一个事件循环,此时用Direct Connection是不安全,原因跟调用一个对象的函数,而这个对象处于另外的线程,那样的调用是不安全。
QObject::connect() 本身是线程安全的。
下面通过结果例子验证一下:
class Receiver:publicQObject
{
Q_OBJECT
public:
voidsendmes()
{
emitemitSignal("emit message from A To B");
}
Receiver()
{
}
protected slots:
voidmessageSlot(QString mes)
{
qDebug()<<mes;
}
signals:
voidemitSignal(QString mes);
private:
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Receiver objA,objB;
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));
qDebug()<<"beforeemitsignal ";
objA.sendmes();
qDebug()<<"afteremitsignal ";
returna.exec();
}
objA,objB;出于同一个线程,所以connect的连接类型是Direct Connection
由输出我们可以看出执行顺序,
如果我们写了两句连接:
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));
就会相应的有两句消息输出:
如果指定了连接类型Qt::UniqueConnection ,就会只有一句消息输出了。
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)),Qt::UniqueConnection );
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)),Qt::UniqueConnection);
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QThread *thread = new QThread;
thread->start();
Receiver objA,objB;
objB.moveToThread(thread);
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)) );
qDebug()<<"beforeemitsignal ";
objA.sendmes();
qDebug()<<"afteremitsignal ";
returna.exec();
}
如果我们把objB放到另外的线程,connect的连接类型应该是Queued Connection 。
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QThread *thread = new QThread;
thread->start();
Receiver objA,objB;
objB.moveToThread(thread);
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)) ,Qt::BlockingQueuedConnection);
qDebug()<<"beforeemitsignal ";
objA.sendmes();
qDebug()<<"afteremitsignal ";
returna.exec();
}
显示的指定连接类型为Qt::BlockingQueuedConnection,则输出为:
QThread 与 QObject的关系(QObject可以用于多线程,可以发送信号调用存在于其他线程的slot函数,但GUI类不可重入)的更多相关文章
- QThread 与 QObject的关系
Threads and QObjects QThread 继承 QObject..它可以发送started和finished信号,也提供了一些slot函数. QObject.可以用于多线程,可以发送信 ...
- QThread::wait(),一直以来我以为它阻塞的是QThread对象,可是我现在明白,原来阻塞的是这个对象所在的线程(通常是主线程)——所有事情源于 QThread 的事件循环——如果使用继承QThread这一方法,QThread::quit()没有效果,因为这个线程根本就不需要事件循环
近日,使用QThread,一些问题百思不得其解,看过大牛的文章,恍然大悟啊. 原文 http://hi.baidu.com/dbzhang800/item/c14c97dd15318d17e1f46f ...
- Qt 使用 lambda 表达式做为槽函数时为什么使用 QObject::sender() 获取到的发送信号对象指针为空?
/*! Returns a pointer to the object that sent the signal, if called in a slot activated by a signal; ...
- [ jquery 过滤器 hasClass(class) ] 此方法用于在选择器的基础之上检查当前的元素是否含有某个特定的类,如果有,则返回true
此方法用于在选择器的基础之上检查当前的元素是否含有某个特定的类,如果有,则返回true 实例: <!DOCTYPE html> <html lang='zh-cn'> < ...
- 简化版可用于多线程的logger
logger 嘛要高效,要简单.废话不多话. GitHub 地址 https://github.com/goldli/logger 本文所说的logger使用System.Logger做为NameSp ...
- pyqt5 使用 QTimer, QThread, pyqtSignal 实现自动执行,多线程,自定义信号触发。
渣渣用法,请等待我心情好的时候更新. 1.第一个例子 1.1 先看mainwindow.py from PyQt5 import QtCore, QtGui, QtWidgets class Ui_M ...
- import 语句用于导入从外部模块,另一个脚本等导出的函数,对象或原语。
import 语句用于导入从外部模块,另一个脚本等导出的函数,对象或原语. 注意:此功能目前无法在任何浏览器中实现.它在许多转换器中实现,例如 Traceur Compiler , Babel , R ...
- 'QObject& QObject::operator=(const QObject&)' is private——无法将自定义的QObject子类放入Qt容器(container)中
先贴出问题的代码: #include<QCoreApplication> classMyObject:publicQObject { public: MyObject(QObject*pa ...
- 多线程中的event,用于多线程的协调
''' 简单的需求:红绿灯,红灯停,绿灯行 一个线程扮演红绿灯,每过一段时间灯变化,3-5个线程扮演车,红灯停,绿灯行 红绿灯线程和车的线程会相互依赖 这种场景怎么实现?---事件 切换一次灯就是一次 ...
随机推荐
- BZOJ3231(矩阵连乘,稍有点复杂)
题目:3231: [Sdoi2008]递归数列 题意: 一个由自然数组成的数列按下式定义: 对于i <= k:ai = bi 对于i > k: ai = c1ai-1 + c2ai-2 ...
- 正则RegEXp
JavaScript RegExp 对象 RegExp 对象 RegExp 对象表示正则表达式,它是对字符串执行模式匹配的强大工具. 直接量语法 /pattern/attributes 创建 RegE ...
- c 这题做了半天,虽然做好了,但是思路还是不清晰,估计让我再做一次还是比较花时间的。
输入一个大写字符,如F 比如: 输入:F 输出: FEDCBA EDCBAB DCBABC CBABCD BABCDE ABCDEF 输入 B 输出: BA AB #include<stdio. ...
- leetcode 211. Add and Search Word - Data structure design Trie树
题目链接 写一个数据结构, 支持两种操作. 加入一个字符串, 查找一个字符串是否存在.查找的时候, '.'可以代表任意一个字符. 显然是Trie树, 添加就是正常的添加, 查找的时候只要dfs查找就可 ...
- C语言宏的特殊用法和几个坑(转)
总结一下C语言中宏的一些特殊用法和几个容易踩的坑.由于本文主要参考GCC文档,某些细节(如宏参数中的空格是否处理之类)在别的编译器可能有细微差别,请参考相应文档. 宏基础 宏仅仅是在C预处理阶段的一种 ...
- 用JS判断用户使用的是手机端还是pc端访问
最近项目中用到一个应用,当访问同一个网站地址的时候,例如:www.xxx.com的时候,如果当前客户端是pc则跳转到专注于pc的部分,如果当前客户机是手机,则跳转到专注于手机的部分,秉承一贯的习惯,b ...
- Java学习之自定义异常
1 package com.gh; import java.util.Scanner; /** * 自定义异常 * @author ganhang * */ public class Exceptio ...
- Co-prime(容斥)
Co-prime Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total S ...
- H264解码的一个測试程序
网上看到的一个H264视频格式的解码測试程序,能够用来參考其逻辑流程. 代码例如以下: Test_Display_H264(){ in_fd = open(H264_INPUT_FILE, ...
- java.text.NumberFormat使用方法
NumberFormat 是全部数值格式的抽象基类. 该类提供了格式化和分析数值的接口. NumberFormat 也提供了确定 哪个语言环境具有数值格式以及它们名字的方法. package com. ...