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子类,例如QTimerQTcpSocketQUdpSocket 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类,例如(QTimerQTcpSocket, 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,则输出为:

http://blog.csdn.net/hai200501019/article/details/9748173

时间: 2024-10-12 15:26:26

QThread 与 QObject的关系(QObject可以用于多线程,可以发送信号调用存在于其他线程的slot函数,但GUI类不可重入)的相关文章

QThread 与 QObject的关系?

Threads and QObjects QThread 继承 QObject..它可以发送started和finished信号,也提供了一些slot函数. QObject.可以用于多线程,可以发送信号调用存在于其他线程的slot函数,也可以postevent给其他线程中的对象.之所以可以这样做,是因为每个线程都有自己的事件循环. 在进行下面的讲解之前,应该了解的重要的一点是:QThread 对象所在的线程,和QThread 创建的线程,也就是run()函数执行的线程不是同一个线程.QThrea

&#39;QObject&amp; QObject::operator=(const QObject&amp;)&#39; is private——无法将自定义的QObject子类放入Qt容器(container)中

先贴出问题的代码: 1 #include<QCoreApplication> 2 classMyObject:publicQObject 3 { 4 public: 5 MyObject(QObject*parent =0): 6 QObject(parent) 7 { 8 } 9 private: 10 int m_id; 11 }; 12 int main(int argc,char*argv[]) 13 { 14 QCoreApplication a(argc, argv); 15 QL

[ jquery 过滤器 hasClass(class) ] 此方法用于在选择器的基础之上检查当前的元素是否含有某个特定的类,如果有,则返回true

此方法用于在选择器的基础之上检查当前的元素是否含有某个特定的类,如果有,则返回true 实例: <!DOCTYPE html> <html lang='zh-cn'> <head> <title>Insert you title</title> <meta http-equiv='description' content='this is my page'> <meta http-equiv='keywords' content

python Queue模块用于多线程通信

# -*-coding:utf-8 -*- import Queue import threading import time q = Queue.Queue(100000) def producer():     for i in range(1000):         q.put(i)         time.sleep(0) def consumer():     for i in range(1000):         print q.get(), q.qsize()       

友元——友元可以访问与其有好友关系的类中的私有成员。 友元包括友元函数和友元类。

简介:友元可以访问与其有好友关系的类中的私有成员.    友元包括友元函数和友元类. [1]将普通函数声明为友元函数 #include<iostream> using namespace std; class Time { public: Time(int,int,int); friend void display(Time &);//定义友元函数 private: int hour; int minute; int sec; }; Time::Time(int h,int m,int

简化版可用于多线程的logger

logger 嘛要高效,要简单.废话不多话. GitHub 地址 https://github.com/goldli/logger 本文所说的logger使用System.Logger做为NameSpace; 一.对象说明    Dll里logger共有两类对象    1.Logger 日志输出控件类.负责启用与停用日志功能.    2.Log 日志记录类.    二.启用/停用日志    Logger.Instance.Start();    Logger.Instance.Stop().Di

多线程中的event,用于多线程的协调

''' 简单的需求:红绿灯,红灯停,绿灯行 一个线程扮演红绿灯,每过一段时间灯变化,3-5个线程扮演车,红灯停,绿灯行 红绿灯线程和车的线程会相互依赖 这种场景怎么实现?---事件 切换一次灯就是一次事件,这次事件的变化造成其他状态的变化 事件是用于线程之间的数据同步的.事件变化其他线程相应作出变化 应该设置一个全局变量当做灯,车的线程监测全局变量. 事件就是封装的一个全局变量 event = threading.event() event.set() 设置标志位,该状态下程序激活,"绿灯&qu

C# 继承后 函数重写父子类调用关系的逻辑

首先吐槽一下自己,做完实验之后,居然忘记到底是什么原因导致想搞清楚这个东西了.有点本末倒置. 首先一点,两种重写函数语法上的差异. new 是不看父类怎么写的,直接就写了.Override 是要父类声明 abstract virtual 之类可以重写的标签才能重写. 首先看看父类的结构 using System; namespace HixLearnCSharp { /// <summary> /// 爸爸类 /// </summary> class ParentClass { /

哪一个对象可以用于获得浏览器发送的请求。

A.HttpServletRequest B.HttpServletResponse C.HttpServlet D.Http 解答:A HttpServletRequest中有一些方法可以获取浏览器发送的请求信息. 原文地址:https://www.cnblogs.com/borter/p/9552943.html