QT多线程淘酒,持续更新

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhejfl/article/details/78799151

第一阶段

首先必须区分三个概念:

主线程:在程序初始化完成后,主线程就进入了main()函数开始执行应用代码,一般在主线程上构建界面对象并呈现之,然后就进入了事件循环以处理各类消息(控件绘制、用户输入、系统输出等消息)。这就是熟知的事件驱动模型。

工作线程:也就是子线程.

以QThread为例说明,QThread 对象关联(依附)于创建它的线程,当QThread对象调用start()方法时,会默认调用它的run()方法。在run()是子线程中。因此在QThread的构造方法中创建的对象都不属于子线程,而属于创建QThread对象的线程。

QObject::moveToThread(QThread *targetThread) 为例说明,只要targetThread在start()以后,该QObject的槽函数都在targetThread子线程中运行。此时targetThread的run()函数已经进入消息循环(非while(1))。

关联(依附)线程:对于对象obj而言,obj.thread()方法可以返回该对象所依附的线程。

改变对象所依附的线程,用QObject::moveToThread(QThread *targetThread) 这样obj及其子对象就依附到了targetThread线程,它的信号槽处理也在targetThread线程中处理。如果obj有父对象,则移动失败。

碰到这样的问题:QObject: Cannot create children for a parent that is in a different thread.

是因为obj的Init方法中子对象没有确定其父对象所造成的。

 myserial = new QSerialPort(); 
改为
 myserial = new QSerialPort(this);
QSerialPort(QObject *parent = Q_NULLPTR) 不指定parent,默认是Q_NULLPTR,
好的习惯是子对象创建时自觉加上this,这样方便Qt管理,当销毁父对象,检查子对象列表,不会出现遗落,造成内存泄漏(内存泄漏
多指是程序内部,该销毁的内存没有销毁)。

在使用继承QThread的方法来创建新线程,必须了解一条规则:
		QThread中只有run()方法是在新线程,其他所有方法都在创建QThread的线程里。

在UI主线程中调用QThread的非run()方法,和执行UI线程普通方法无差别。但是,如果这个函数要对QThread的某个变量进行变更,而
这个变量在run()函数也用的话,这时需要考虑加锁。

QT子线程写法技巧:
1、尽量不要在run()方法中写死循环,对于要持续做的事情,可以做个定时器,banding到timeout信号上;
2、写一个object,用moveToThread()方法关联到目标线程;
3、对于object,对外的所有操作,全部用信号槽。通过槽接收外部的调用操作(其他线程发出信号,槽函数在此线程中得到相应);通过信号
发送数据给外部(比如接收到的数据);
4、需要在子线程分配的资源,比如QSerialPort,可以通过该object的某个槽函数(如slotInit())中进行,将其banding到线程对象的started()
信号上。也就排除了这个问题:QObject: Cannot create children for a parent that is in a different thread.
5、程序退出,如何销毁object?
6、对于线程对象(why),以及Move到线程里的对象(否则move失败),都不要设置parent。


/******************************************************/
 
 
第二阶段——线程管理

一、线程优先级 enum QThread::Priority       
该枚举类型表明操作系统如何去安排新创建的线程。
QThread::IdlePriority——只有在没有其他线程在running时才调度
QThread::LowestPriority《QThread::LowPriority《QThread::NormalPriority<<QThread::HighPriority<<
QThread::HighestPriority<<QThread::TimeCriticalPriority 调度频率有低到高到尽可能频繁的调度
QThread::Inheritpriority:default used the same priority as the creating thread
 
二、线程启动
    启动调用start(Priority priority=Inheritpriority),调用后会执行run(),但在run()函数执行前
还会发出started()信号——初始化好用的很。 线程是操作系统调度的最小单位,根据优先级调度。优先级参数的
效果还是取决于操作系统的调度策略。特别是哪些不支持线程优先级的系统,优先级会被忽略。
-------具体看

三、线程执行
 int exec()    进入事件循环并等待直到调用exit()。返回值是通过调用exit()来获得,如果调用成功则返回0;
 virtual void run()    ---线程的开始,一般用while(1)
    

、线程退出
void quit()
 告诉线程事件循环退出,返回0表示成功===调用QThread::exit();
void exit(int returnCode = 0)
调用这个函数后,线程离开事件循环后返回。
 
 
 
 
危险的void termiante()   终止线程,线程可能会立即被终止也可能不会,这取决于操作系统的调度策略,使用terminate()之后
再使用QThread::wait()确保万无一失。
在非必要时,不使用这一招。 

/*************************************************************************************/
 
 
 
 
第三部分——线程同步问题
使用线程的主要想法是希望它们可以尽可能地并发执行,而在一些关键点上线程之间需要停止或等待。
方法1:使用全局变量 利用QMutex实现Qt线程间共享数据——共享内存
方法2:使用signal/slot机制,把数据从一个线程传递到另一个线程。
方法3:环形缓冲区(ring buffer), 环形队列(ring queue)多用于2个线程之间传递数据,是标准的先入先出(FIFO)模型。
一般来说,对于多线程共享数据,需要使用Mutex来同步,如此共享数据才不至于发生不可预测的修改/读取,虽然
Mutex的使用也带来了额外的系统开销,而引入ring buffer/queue,也是为了有效地解决这个问题。 ring buffer
/queue,因其特殊的结构及算法,可用于2个线程共享数据的同步,而且必须遵循A线程push in, B线程pull out的
原则。  采用环形缓冲区/队列这种机制,另一个好处是A push完数据后可以马上接收另一个数据,当输入数据较快
时,也不会造成因数据阻塞而丢包。
线程A               媒介             线程B
data in  --》ring buffer/queue---> data out
ring buffer/queue 原理见于另一篇文章——Ring Buffer/Queue原理。

QT信号与槽
这是三个相关的连接函数
法1、[static] QMetaObject::Connection QObject::connect(const QObject *sender,\
 const char *signal, const QObject *receiver, const char *method, 
Qt::ConnectionType type = Qt::AutoConnection)
这是静态函数,你必须使用SIGNAL()和SLOT()宏命令来指定参数signal和method。
例子QObject::connect(scrollBar, SIGNAL(valueChanged(int)), label,  SLOT(setNum(int)));
 
 
法2、[static]QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal,\
 const QObject *receiver, const QMetaMethod &method,\
 Qt::ConnectionType type = Qt::AutoConnection)
 
 

这也是一个静态函数,与法1类似,只是signal和method都是QMetaMethod类型的对象,

扫描二维码关注公众号,回复: 3596554 查看本文章

法3、QMetaObject::Connection QObject::connect(const QObject *sender, \
const char *signal, const char *method, Qt::ConnectionType 
type = Qt::AutoConnection) const
与Equivalent to connect(sender, signal, this, method, type)相同

法4、[static] QMetaObject::Connection QObject::connect(const QObject *sender,\
 PointerToMemberFunction signal, const QObject *receiver, \
PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)

例子 QObject::connect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);

这是线程安全的。一个信号和多个槽连接,但发出emit 信号,这些槽按照connection顺序来激活。
1、它支持编译器件检查信号和槽是否存在,它们的类型,及Q_QBJECT是否丢失
2、参数能被teypdef或不同命名空间指定
3、如果有隐式转换的参数,会自动转换类型。
4、不仅能指定槽函数,还可以链接QObject下的任何成员函数。
虽然有五种:
 
 
先讲一讲参数 Qt::ConnectionType  type (5种)
默认是Qt::AutoConnection  就是自动根据情况来,在信号发出时决定。
Qt::DirectConnection:接收者receiver和emit信号是在一个线程中,用直接连接,
发出信号后,立即调用槽函数。
Qt::QueuedConnection:槽函数在CPU时间片轮到接受者reciver所在线程时调用,槽
函数运行在接收者reciver所在线程。------这就是我们在多线程中用moveToThread()来
改变依附线程的原因。改变后,该objcet的槽函数就可以在子线程中执行,减少UI线程
的耗时操作。----实现跨线程
Qt::BlockingQueuedConnection:和Qt::QueuedConnection类似,不同之处在于
emit发出信号的线程阻塞直到槽函数返回。这种类型当然不合适发出信号和receiver在
同一个线程的情况,否则就发生死锁deadLock。
Qt::UniqueConnection:是以上连接类型的OR结合版,如果有链接存在,就失败。

槽函数
是普通的C++成员函数,可以被正常调用,它们唯一的特殊性就是可以与信号相连。
当与之关联的信号被发射时,这个槽就会被调用。
槽函数与普通成员函数一言,有public、private、protected等特性。
元对象编译器moc(meta object compiler)对C++文件中的类声明进行分析并产生用于
初始化元对象的C++代码,元对象包含全部信号和槽的名字以及指向这些函数的指针。
moc读C++源文件,如果发现有Q_OBJECT宏声明的类,它就会生成另外一个C++源
文件
 
 

猜你喜欢

转载自blog.csdn.net/zhejfl/article/details/78799151