QThread继承于QObject,它会发送信号标识线程的开始与结束。更有趣的是QObjects会用于多线程中,发送的信号会调用另外线程中的槽函数、发送事件给另外线程的对象。实现这种机制的基础:每个线程可以拥有独自的事件循环。
QObject是可重入的。大部分非GUI类的子类,如QTimer、QTcpSocket、QUdpSocket、QProcess等也是可重入的,多线程可同时使用这些类。记住一点:这些类被设计成在一个线程中创建和使用的。在一个线程中创建对象,但是调用另外一个线程中的方法,行为不保证一定有效!由三点限制需要注意:
尽管QObject是可重入的,但是GUI类-QWidget及其子类是不可重入的。他们只能用于主线程中,并且在主线程启动前必须调用QCoreApplication::exec()。
一个线程中的事件循环,可以使该线程使用非GUI的类(需要在事件循环中运行,如QTimer、QTcpSocket、QUdpSocket等),并且可以将信号连接到其他线程的槽函数中。
如果事件循环未运行,事件不会分发到Object中。例如,在线程中创建QTimer对象,但是没有调用exec(),该Timer对象不会发送信号timeOut()。同样的,调用deleteLater也不会生效(这些限制也在主线程中同样适用)。
可以在任意时刻任意线程中,使用线程安全的方法QCoreApplication::postEvent()向对象发送事件。该事件会被自动的分发到创建该对象所在线程的事件循环中。所有线程都支持事件过滤器,限制是:监控对象和被监控对象必须在同一个线程中。QCoreApplication::sendEvent() 发送的事件要求发送方和接收方在同一个线程中。
QObject及其子类都不是线程安全的。这包括整个事件传递系统。记住一点:当你从其他线程访问对象时,事件循环会传递事件到你的QObject子类。如果在不是当前线程的类上调用一个方法,该对象可能接收事件,因此必须要对内部共享变量加锁,否则会发生非预期行为。
QThead对象存活于该对象创建所在的线程,因此线程子类中的槽函数要对共享变量加锁。另一方面,可以在QThread::run实现中发送信号,因为发送信号时线程安全的。
有如下场景:需要在子线程中定时的执行某些操作,操作的结果更新到主线程中。如何实现呢?以下是详细的代码实现:
以下是头文件:
#pragma once
//
//在子线程中执行定时的操作
#include
#include
#include
#include
//子线程工作单元
class TimerWorkerItem : public QObject
{
Q_OBJECT
public:
TimerWorkerItem();
public slots:
void startTimer();
void endTimer();
private slots:
void doSomething();
private:
QTimer* m_pTimer;
};
//主线程控制类
class Manager : public QObject
{
Q_OBJECT
public:
Manager();
signals:
void startTheadTimer();
void endTheadTimer();
private:
TimerWorkerItem* m_pWorkerItem;
QThread m_WorkderThread;
};
void testTimerThreadDemo();
以下是源文件:
#include "TimerThreadDemo.h"
//
//子线程工作单元
TimerWorkerItem::TimerWorkerItem()
:m_pTimer(nullptr)
{
//该对象在主线程中构造
qDebug() << "TimerWorkerItem::TimerWorkerItem MainThread:" << QThread::currentThread();
}
void TimerWorkerItem::startTimer()
{
if (!m_pTimer)
{
//在子线程中实例化定时器
m_pTimer = new QTimer;
m_pTimer->setInterval(2000);
connect(m_pTimer, SIGNAL(timeout()), this, SLOT(doSomething()));
qDebug() << "TimerWorkerItem::startTimer ChildThread:" << QThread::currentThread();
}
if (!m_pTimer->isActive())
m_pTimer->start();
}
void TimerWorkerItem::endTimer()
{
if (m_pTimer->isActive())
{
//在子线程中释放定时器
m_pTimer->stop();
delete m_pTimer;
qDebug() << "TimerWorkerItem::endTimer ChildThread:" << QThread::currentThread();
}
}
void TimerWorkerItem::doSomething()
{
//在子线程中执行槽函数
qDebug() << "to do something...";
qDebug() << "TimerWorkerItem::doSomething ChildThread:" << QThread::currentThread();
}
//
//主线程控制类
Manager::Manager()
{
//不能设置父对象!!!否则不能转移对象的线程归属!
m_pWorkerItem = new TimerWorkerItem;
//移动对象所在线程
m_pWorkerItem->moveToThread(&m_WorkderThread);
//绑定主子线程的信号槽
connect(&m_WorkderThread, SIGNAL(finished()), m_pWorkerItem, SLOT(deleteLater()));
connect(this, SIGNAL(startTheadTimer()), m_pWorkerItem, SLOT(startTimer()));
connect(this, SIGNAL(endTheadTimer()), m_pWorkerItem, SLOT(endTimer()));
//启动线程
m_WorkderThread.start();
qDebug() << "Manager::Manager MainThread:" << QThread::currentThread();
//方式1. 调用子线程中函数,直接调用另外线程中的函数A,函数A会在主线程中执行,这样是不对的。
//m_pWorkerItem->startTimer();
//方式2. 调用子线程中函数,通过信号槽的方式调用另外线程中的函数A,这样才是对的。
emit startTheadTimer();
}
//o
void testTimerThreadDemo()
{
Manager* pManager = new Manager;
}
请注意上述调用子线程中的函数不同方式。
TimerWorkerItem::TimerWorkerItem MainThread: QThread(0x11ac1f8) //对象所在线程默认是:创建该对象时的所在线程
Manager::Manager MainThread: QThread(0x11ac1f8)
TimerWorkerItem::startTimer ChildThread: QThread(0x11ac1f8) //这个是在主线程中执行的,不对。
to do something...
TimerWorkerItem::doSomething ChildThread: QThread(0x11d1014)
TimerWorkerItem::TimerWorkerItem MainThread: QThread(0xffa6f8) //对象所在线程默认是:创建该对象时的所在线程
Manager::Manager MainThread: QThread(0xffa6f8)
TimerWorkerItem::startTimer ChildThread: QThread(0x1020dd4) //这个是在子线程中执行的,正确。
to do something...
TimerWorkerItem::doSomething ChildThread: QThread(0x1020dd4)
通过上述的案例,使用了QThread、moveToThread、工作单元、定时器的协同处理。
Qt支持如下几种信号槽连接类型:
通过Connect()函数能够指定信号槽的连接形式。如果发送者和接收者不在同一个线程,连接类型用直接连接,这样就是不安全的。同理,直接调用其他线程中的对象的任何函数都是不安全的。QObject::connect本身是线程安全的。