背景描述:

以前,继承 QThread 重新实现 run() 函数是使用 QThread唯一推荐的使用方法。这是相当直观和易于使用的。但是在工作线程中使用槽机制和Qt事件循环时,一些用户使用错了。Qt  核心开发人员Bradley T. Hughes, 推荐使用QObject::moveToThread 把它们移动到线程中。不幸的是, 以用户反对这样使用。Olivier Goffart, 前Qt  核心开发人之一, 告诉这些用户你们不这样做就错了。最终这俩种用法我们都在QThread的文档中发现 。

QThread::run() 是线程的入口点

从Qt文档中我们可以看到以下内容:

A QThread instance represents a thread and provides the means to start() a thread, which will then execute the reimplementation of QThread::run(). The run() implementation is for a thread what the main() entry point is for the application.

Usage 1-0

在新的线程中执行一些代码,继承QThread 重新实现 run()函数接口。

For example

#include <QtCore>

class Thread : public QThread
{
private:
void run()
{
qDebug()<<"From worker thread: "<<currentThreadId();
}
}; int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId(); Thread t;
QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit())); t.start();
return a.exec();
}

结果输出如下:

From main thread:  0x15a8
From worker thread: 0x128c

Usage 1-1

正因QThread::run() 是线程的入口, 所以很容易的理解它们, 并不是所有的代码都在run()接口中被直接调用而不在工作线程中被执行。

接下来的例子中,成员变量 m_stop 在 stop() 和 run()都可被访问到。考虑到前者将在主线程执行,后者在工作线程执行,互斥锁或其它操作是有必要的。

#if QT_VERSION>=0x050000
#include <QtWidgets>
#else
#include <QtGui>
#endif class Thread : public QThread
{
Q_OBJECT public:
Thread():m_stop(false)
{} public slots:
void stop()
{
qDebug()<<"Thread::stop called from main thread: "<<currentThreadId();
QMutexLocker locker(&m_mutex);
m_stop=true;
} private:
QMutex m_mutex;
bool m_stop; void run()
{
qDebug()<<"From worker thread: "<<currentThreadId();
while (1) {
{
QMutexLocker locker(&m_mutex);
if (m_stop) break;
}
msleep(10);
}
}
}; #include "main.moc"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId();
QPushButton btn("Stop Thread");
Thread t; QObject::connect(&btn, SIGNAL(clicked()), &t, SLOT(stop()));
QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit())); t.start();
btn.show();
return a.exec();
}

结果输出如下:

From main thread:  0x13a8
From worker thread: 0xab8
Thread::stop called from main thread: 0x13a8

你可以看到Thread::stop() 函数是在主线程中被执行的。

Usage 1-2 (错误的使用方式)

以上的例子很容易明白,但它不是那么直观当事件系统(或队列)中引进工作线程时。

例子如下, 我们应该做些什么,如果我们想在工作线程中周期性的做些动作?

  • 在Thread::run()中创建一个QTimer
  • 将超时信号连接到线程中的槽函数上
#include <QtCore>

class Thread : public QThread
{
Q_OBJECT
private slots:
void onTimeout()
{
qDebug()<<"Thread::onTimeout get called from? : "<<QThread::currentThreadId();
} private:
void run()
{
qDebug()<<"From worker thread: "<<currentThreadId();
QTimer timer;
connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
timer.start(1000); exec();
}
}; #include "main.moc" int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId(); Thread t;
t.start(); return a.exec();
}

乍看起来代码没什么问题。当线程开始执行时, 我们在当前线程的事件处理中启动了一个定时器。我们将 onTimeout() 连接到超时信号上。此时我们希望它在线程中执行?

但是执行结果如下:

From main thread:  0x13a4
From worker thread: 0x1330
Thread::onTimeout get called from?: 0x13a4
Thread::onTimeout get called from?: 0x13a4
Thread::onTimeout get called from?: 0x13a4

Oh, No!!! 它为什么在主线程中被调用了!

是不是很有趣?(接下来我们将要讨论这是为什么)

如何解决这个问题

为了使槽函数工作在线程中, 有人尝试在connect()函数中传入参数 Qt::DirectConnection

connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()), Qt::DirectConnection);

还有人尝试在线程构造函数中添加如下功能。

moveToThread(this)

它们都会如期望的工作吗. 但是 …

第二个用法的是错误的,

尽管看起来它工作啦,但很令人费解,这不是QThread 设计的本意(QThread 中所有实现的函数是被创建它的线程来调用的,不是在线程中

实际上,根据以上表述,第一个方案是错误的。onTimeout() 是线程对象的一个成员函数,会被创建它的线程来调用。

它们都是错误的使用方法?!我们该如何做呢?

Usage 1-3

因为没有一个线程类的成员是设计来被该线程调用的。所以如果我们想使用槽函数必须创建一个独立的工作对象。

#include <QtCore>

class Worker : public QObject
{
Q_OBJECT
private slots:
void onTimeout()
{
qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();
}
}; class Thread : public QThread
{
Q_OBJECT private:
void run()
{
qDebug()<<"From work thread: "<<currentThreadId();
QTimer timer;
Worker worker;
connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
timer.start(1000); exec();
}
}; #include "main.moc" int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId(); Thread t;
t.start(); return a.exec();
}

结果如下:

From main thread:  0x810
From work thread: 0xfac
Worker::onTimeout get called from?: 0xfac
Worker::onTimeout get called from?: 0xfac
Worker::onTimeout get called from?: 0xfac

问题解决啦!

尽管运行的很好,但是你会注意到,当工作线程中运行在事件循环 QThread::exec() 中时,在QThread::run() 函数接口中没有执行自身相关的事务。

所以我们是否可以将对象的创建从QThread::run()中移出, 此时, 槽函数是否依旧会被QThread::run()调用?

Usage 2-0

如果我们只想使用QThread::exec(), 默认情况下会被QThread::run() 调用, 那不需要子类化QThread。

  • 创建一个工作对象
  • 建立信号与槽的连接
  • 将工作对象移至子线程中
  • 启动线程
#include <QtCore>
#include <QUdpSocket> class Worker : public QObject
{
Q_OBJECT
public:
  void Work()
  {
    
  }
private:
  QUdpSocket *socket;
private slots:
void onTimeout()
{
qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();
}
}; #include "main.moc" int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId(); QThread t;
QTimer timer;
Worker worker; QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
timer.start(1000); timer.moveToThread(&t);
worker.moveToThread(&t); t.start(); return a.exec();
}

结果是:

From main thread:  0x1310
Worker::onTimeout get called from?: 0x121c
Worker::onTimeout get called from?: 0x121c
Worker::onTimeout get called from?: 0x121c

正如所预料的,槽函数没有在主线程中执行。

在此例中,定时器和工作对象都被移至子线程中,实际上,将定时器移至子线程中是没有必要的。

Usage 2-1

在上面的例子当中将 timer.moveToThread(&t); 这一行注释掉。

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId(); QThread t;
QTimer timer;
Worker worker; QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
timer.start(1000); // timer.moveToThread(&t);
worker.moveToThread(&t); t.start(); return a.exec();
}

不同之处如下:

在上面的例子中,

  • 信号 timeout() 是由子线程发送出去的。
  • 定时器和工作对象是在同一线程当中的,它们的连接方式直接连接。
  • 槽函数的调用和信号的触发是在同一线程中的。

在本例中,

  • 信号timeout() 是由主线程发出的。
  • 定时器和工作对象是在不同的线程中,它们的连接方式队列连接。
  • 槽函数是在子线程中被调用。
  • 由于队列连接机制的存在,可以安全的将信号和槽函数在不同的线程中连接起来。如果所有的跨线程通信都通过队列连接方式,那么多线程的互斥防范机制将不在需要。

总结

  • 子类化QThread ,实现 run()函数接口是很直观的,也存在很多不错的方法去子类化QThread,但是在工作线程中处理事件处理并不是一件简单的事。
  • 在事件处理存在时使用对象时将它们移至线程中时很简单的,这样将事件处理和队列连接的细节给隐藏了。
 
 
转自:http://blog.csdn.net/zhenwo123/article/details/40861171
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
自我理解(重点):
理解:如QThread thread; A.moveToThread(&thread),moveToThread() 是改变了对象A的线程依附性关系。将对象A及其内部的成员变量的所属线程切换到次线程thread中。注:可以通过调用QObject::thread()可以查询一个QObject的线程依附性,通过QThread::currentThread()可以查询当前运行所属线程,都非常有用。
如代码:
QThread *thread;
thread=new QThread(this);
qDebug()<<"1:"<<commUdpClient->thread();
commUdpClient->moveToThread(thread);
thread->start();
qDebug()<<"2:"<<commUdpClient->thread();

输出:

说明对象commUdpClient确实发生所在线程的变化。但是值得注意的是,commUdpClient对象中的

    QUdpSocket *socket;
   QUdpSocket *socket2;
QUdpSocket socket3; 前两个指针在构造函数里和槽函数里初始化,所属线程是有很大区别的。如下面代码所示,在构造函数里初始化socket2,意味着属于主线程,不管commUdpClient是否移到子线程,它还是属于主线程;而socket是在commUdpClient对象移到子线程后,再在槽函数中初始化,则属于子线程;socket3默认在构造函数里初始化,即使构造函数没有显式初始化,但是也属于主线程,其它如int、char等基础数据变量也是属于主线程的。所以在利用moveToThread方式使用多线程的情况下,commUdpClient类声明中类类型对象建议都声明为指针,然后都在某个槽函数中初始化,而基础数据变量如int、char等建议声明为private,如果需要改变则利用属性的方式get、set来改变,实在需要使用public,则需要加锁,不然会出错。 
 
原则:1、采用信号方式调用通过moveToThread()方法移到另一个线程中运行的对象的槽方法。
    2、不要在该对象A的构造函数中初始化指针变量B(如B=new QUdpSocket()),因为对象A虽然移到了子线程中,只是代表所有的槽函数在子线程ThreadSon中运行,但是对象A构造的过程是在创建该对象的线程ThreadParent中完成的,也即意味着如果在构造函数中初始化指针变量,则该指针指向的内存还是属于线程ThreadParent,而非线程ThreadSon,这与我们的初衷不相符合,尤其是这些对象要在槽函数中使用,会报错如:

所以要求A的初始化过程放到某个槽函数中进行,包括connect的过程,然后由线程ThreadParent中调用A的对象C通过信号的方式触发该槽。A其它的槽要想在对象C中调用也需要通过信号的方式,如果通过直接调用A.XX()方式调用槽函数或者其它函数,则仍旧是在线程ThreadParent中运行,非在线程ThreadSon中运行。切记切记,具体看以下代码:

对象B的源码.h

//CommUdpClient.h

#include <QObject>
#include <QUdpSocket> class CommUdpClient : public QObject
{
Q_OBJECT
public:
explicit CommUdpClient(QObject *parent = 0);
~CommUdpClient();
QUdpSocket *socket;
   QUdpSocket *socket2;
QUdpSocket socket3; private:
QHostAddress serverAddress;
quint16 serverPort; public:
//发送报文
void send(const QByteArray &msg); signals:
//接收完报文后触发的信号,用于上层触发相应的槽,获得报文
void sigRevedMsg(QByteArray msg,const QStringList &paras); public slots:
//报文发送完成信号bytesWritten对应的槽
void sltSent(qint64 bytes);
//接收报文,对应信号readyRead的槽
QByteArray sltReceive();
//通讯参数设置,打开连接
void sltOpenConnect(const QStringList &paras); };

对象B的源码.CPP

#include "commudpclient.h"
#include <qstringlist.h>
#include <QMetaEnum>
#include <QDebug>
#include <QThread> CommUdpClient::CommUdpClient(QObject *parent) :
QObject(parent)
{
qDebug()<<"udp current thread 1:"<<QThread::currentThread();
//错误
//socket=new QUdpSocket;
//connect(socket,SIGNAL(bytesWritten(qint64)),this,SLOT(sltSent(qint64)));
//connect(socket,SIGNAL(readyRead()),this,SLOT(sltReceive()));
  
socket2=new QUdpSocket;
   qDebug()<<"socket2 thread:"<<socket2->thread();
} CommUdpClient::~CommUdpClient()
{
if(socket!=NULL)
{
if(socket->isOpen())
{
socket->close();
}
delete socket;
socket=NULL;
}
} void CommUdpClient::sltOpenConnect(const QStringList &paras)
{
qDebug()<<"udp current thread 2:"<<QThread::currentThread();
//正确
socket=new QUdpSocket();//这里加this和不加this是有区别的

这是打印出来的socket的parent,是有区别的,不过这里的CommUdpClient也是顶层的,无parent的,不然是无法移动到子线程中的,会报错

,具体在对象B的源码中描述。
    connect(socket,SIGNAL(bytesWritten(qint64)),this,SLOT(sltSent(qint64)));
connect(socket,SIGNAL(readyRead()),this,SLOT(sltReceive()));

  //测试
  qDebug()<<"socket thread:"<<socket->thread();
  qDebug()<<"socket2 thread:"<<socket2->thread();
  qDebug()<<"scoket3 thread:"<<socket3.thread();
  qDebug()<<this<<", thread:"<<this->thread(); serverAddress.setAddress(paras[0]);
serverPort=paras[1].toUShort();
socket->bind(paras[2].toUShort());
} void CommUdpClient::send(const QByteArray &msg)
{
  qDebug()<<"udp send thread:"<<QThread::currentThread();
if(!serverAddress.isNull())
{
socket->writeDatagram(msg,serverAddress,serverPort);
qDebug()<<"client send end";
}
} void CommUdpClient::sltSent(qint64 bytes)
{ } QByteArray CommUdpClient::sltReceive()
{
qDebug()<<"server start receive...";
while (socket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(socket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
socket->readDatagram(datagram.data(), datagram.size(),
&sender, &senderPort);
qDebug()<<datagram;
QStringList sl;
sl.append(sender.toString());
sl.append(QString::number(senderPort));
emit sigRevedMsg(datagram,sl);
return datagram;
}
}

对象A的源码.h

#ifndef COMMUNICATION_H
#define COMMUNICATION_H #include <QObject>
#include <QThread>
#include <qstringlist.h>
#include "commtcpclient.h" class Communication : public QObject
{
Q_OBJECT
public:
explicit Communication(QObject *parent = 0);
virtual ~Communication(); public:
CommUdpClient *commUdpClient; QThread *thread; public:
void readData();
void writeData(); signals:
void sigThreadConnect(const QStringList &paras); public slots:
void sltRevedMsg(QByteArray msg,const QStringList &paras); }; #endif // COMMUNICATION_H

对象B的源码.cpp

#include "communication.h"
#include <QCoreApplication>
#include <QDebug>
#include <QTime> Communication::Communication(QObject *parent) :
QObject(parent)
{
qDebug()<<"current thread:"<<QThread::currentThread();
commUdpClient=new CommUdpClient();//这里不能加this,不然是不允许移到子线程中,报错

    connect(commUdpClient,SIGNAL(sigRevedMsg(QByteArray,QStringList)),this,SLOT(sltRevedMsg(QByteArray,QStringList)));
connect(this,SIGNAL(sigThreadConnect(QStringList)),commUdpClient,SLOT(sltOpenConnect(QStringList))); thread=new QThread(this);
   qDebug()<<"1:"<<commUdpClient->thread();
commUdpClient->moveToThread(thread);
qDebug()<<"2:"<<commUdpClient->thread();
thread->start();
qDebug()<<"3:"<<commUdpClient->thread();
} Communication::~Communication()
{
delete commUdpClient;
thread->quit();
delete thread;
} void Communication::readData()
{
QString sz="192.168.1.186:1234:1235";
QStringList sl=sz.split(':');
emit sigThreadConnect(sl);//正确用法
    //commUdpClient->send("error");//不正确的用法,这个是直接在对象B的线程中运行的
}

void Communication::writeData()
{
qDebug()<<"xxxx";
} void Communication::sltRevedMsg(QByteArray msg)
{
qDebug()<<"communication end,receive msg:"<<msg;
} void Communication::sltRevedMsg(QByteArray msg, const QStringList &paras)
{ }

远行结果:

 //commUdpClient->send("error");//不正确的用法,这个是直接在对象B的线程中运行的

具体的参考资料:http://blog.csdn.net/sydnash/article/details/7425947  一种使用QThread线程的新方法QObject::moveToThread

http://blog.csdn.net/lainegates/article/details/9701215

http://mobile.51cto.com/symbian-268690_1.htm

关于: QObject: Cannot create children for a parent that is in a different thread错误

参考:http://blog.chinaunix.net/uid-26808060-id-3355816.html

class TcpComm:public QThread
{
Q_OBJECT
public:
TcpComm(const QString &iAddrStr, quint16 iPort);
~TcpComm();
........
private:
.......
TcpClient*mTcpClient;
}; TcpComm::TcpComm(const QString &iAddrStr, quint16 iPort):mAddr(iAddrStr), mPort(iPort)
{
mIsStop = false;
mTcpClient = new TcpClient();
start();
}
以上程序在运行时报QObject: Cannot create children for a parent that is in a different thread错误。
将原构造函数中的mTcpClient = new TcpClient();放到run()中问题解决。
TcpComm::TcpComm(const QString &iAddrStr, quint16 iPort):mAddr(iAddrStr), mPort(iPort)
{
mIsStop = false;
start();
} void TcpComm::run()
{
mTcpClient = new TcpClient();
........
}
查了查,原因应该是,在QThread中定义的所有东西都属于创建该QThread的线程。所以在构造函数中初始化的mTcpClient应该是属于父线程的,那么在run中调用时就属于跨线程调用。所以把mTcpClient放到run中初始化就属于线程的了,调用时就不会出现跨线程调用的问题。
另外:QThread中写的所有函数都应该在创建它的线程中调用,而不是开启QThread的线程

其它参考资料:http://blog.csdn.net/zhangbinsijifeng/article/details/52299926  qt中的线程 拥有权 一个对象属于哪个线程

http://blog.csdn.net/an505479313/article/details/50351745  QThread使用——关于run和movetoThread的区别

http://blog.csdn.net/dbzhang800/article/details/6554104  Qt 线程基础(QThread、QtConcurrent等)

https://www.cnblogs.com/liushui-sky/p/5829563.html?tdsourcetag=s_pcqq_aiomsg

重点:怎样正确的使用QThread类(很多详细例子的对比,注意:QThread 中所有实现的函数是被创建它的线程来调用的,不是在线程中)good的更多相关文章

  1. 重点:怎样正确的使用QThread类(注:包括推荐使用QThread线程的新方法QObject::moveToThread)

    背景描述: 以前,继承 QThread 重新实现 run() 函数是使用 QThread唯一推荐的使用方法.这是相当直观和易于使用的.但是在工作线程中使用槽机制和Qt事件循环时,一些用户使用错了.Qt ...

  2. saltstack在jianja模板中,执行salt函数来获取某些变量的值,并且将配置写入到配置文件中?

    问题描述: 通过saltstack的jinja模板方式,可以将变量的值写入到配置文件,即动态获取的方式.这里介绍,通过执行salt函数来获取值的方式. 演示: 1.通过在sls中,增加jinja的模板 ...

  3. SQL中的存储过程和函数

                                        存储过程和函数 简单的说,存储过程就是一条或者多条SQL语句的集合.可以视为批文件,但是其作用不仅仅局限于批处理.本文主要介绍如 ...

  4. 机器学习中的python常用函数

    glob模块 说明: 1.glob是python自己带的一个文件操作相关模块,用它可以查找符合自己目的的文件,就类似于Windows下的文件搜索, 支持通配符操作 *.?.[] 这三个通配符,*代表0 ...

  5. 【并发编程】关于Thread类的详细介绍

    多线程编程基础--Thread类 Thread类是Java中实现多线程编程的基础类.本篇博客就来介绍下Thread类的常用API和常见用法. Thread类常用的方法如下: Thread.active ...

  6. MySQL中的存储过程和函数

    存储过程和函数 简单的说,存储过程就是一条或者多条SQL语句的集合.可以视为批文件,但是其作用不仅仅局限于批处理.本文主要介绍如何创建存储过程和存储函数,以及如何调用.查看.修改.删除存储过程和存储函 ...

  7. 转】SparkSQL中的内置函数

    原博文来自于: http://blog.csdn.net/u012297062/article/details/52207934    感谢! 使用Spark SQL中的内置函数对数据进行分析,Spa ...

  8. 基类中定义的虚函数在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数、参数类型及参数的先后顺序,都必须与基类中的原型完全相同 but------> 可以返回派生类对象的引用或指针

      您查询的关键词是:c++primer习题15.25 以下是该网页在北京时间 2016年07月15日 02:57:08 的快照: 如果打开速度慢,可以尝试快速版:如果想更新或删除快照,可以投诉快照. ...

  9. pyqt5-多线程QThread类

    要实现多线程,我们要先继承QThread类并重新实现其中的run()函数,也就是说把耗时的操作放入run()函数中 import sys from PyQt5.QtCore import Qt, QT ...

随机推荐

  1. Apache Avro:一个新的数据交换格式

    原文: http://blog.cloudera.com/blog/2009/11/avro-a-new-format-for-data-interchange/ 注:由于个人英语能力有限,翻译不准确 ...

  2. Project Euler:Problem 34 Digit factorials

    145 is a curious number, as 1! + 4! + 5! = 1 + 24 + 120 = 145. Find the sum of all numbers which are ...

  3. sama5d3 开入测试

    root@sama5d3-linux:~ echo 20 > /sys/class/gpio/export root@sama5d3-linux:~ echo 16 > /sys/clas ...

  4. 使用js事件机制进行通用操作&特定业务处理的协调

    背景:提供一个通用的功能工具条,工具条会在特定的事件响应时进行一些通用处理:第三方系统使用iframe嵌入这个工具条中,在工具条的特定的事件响应时进行通用处理的时候,有可能第三方系统会有一些自己的业务 ...

  5. C++中数字和字符串的转换

    1.字符串数字之间的转换 (1)string --> char *   string str("OK");   char * p = str.c_str(); (2)char ...

  6. Farey Sequence(欧拉函数)

    题意:给出式子F F中分子分母互质,且分子小于分母 例: F2 = {1/2} F3 = {1/3, 1/2, 2/3} F4 = {1/4, 1/3, 1/2, 2/3, 3/4} F5 = {1/ ...

  7. PHP多进程编程(3):多进程抓取网页的演示

    我们知道,从父进程到子经常的数据传递相对比较容易一些,但是从子进程传递到父进程就比较的困难. 有很多办法实现进程交互,在php中比较方便的是 管道通信.当然,还可以通过 socket_pair 进行通 ...

  8. JSON美化输出

    echo '{"a": 1, "b": 2}' | python -m json.tool 转自: http://blog.csdn.net/chosen0ne ...

  9. mysql 分页sql

    -- pageSize=3 pageIndex total=7 -- 开始行号 startRowNum=(pageIndex-1)*pageSize+1; -- 结束行号 endRowNum=page ...

  10. digitalocean --- How To Install Apache Tomcat 8 on Ubuntu 16.04

    https://www.digitalocean.com/community/tutorials/how-to-install-apache-tomcat-8-on-ubuntu-16-04 Intr ...