QT多线程编程

72.进程与线程的概念

程序是计算机存储系统中的数据文件

  • 源代码程序(文本文件,描述程序行为和功能)
  • 可执行程序(二进制文件,直接加载并执行)

进程的概念

  • 广义:程序关于某个数据集合的一次运行活动
  • 狭义:程序被加载到内存中执行后得到进程

程序和进程的区别

  • 程序是硬盘中静态的文件,由存储系统中的一段二进制表示
  • 进程是内存中动态的运行实体,包含数据段,代码段,PC指针等

程序和进程的联系

  • 一个程序可能对应多个进程,一个程序多次运行,每次运行产生一个进程
  • 一个进程可能包含多个程序,一个程序以来多个其他动态库

在当代操作系统中,资源分配的基本单位是进程;而CPU调度执行的基本单位是线程

线程的概念

  • 进程内的一个执行单元
  • 操作系统中一个可调度的实体
  • 进程中相对独立的一个控制流序列
  • 执行时的现场数据和其他调度所需的信息

可执行程序加载执行的过程

  • 系统分配资源(内存,IO等)
  • 将PC指向main函数入口地址
  • 从PC指针包含的地址处开始执行(第一个线程)

深入理解进程和线程

  • 进程中可以存在多个线程共享进程资源
  • 线程是被调度的执行单元,而进程不是调度单元
  • 线程不能脱离进程单独存在,只能依赖于进程运行
  • 线程有生命期,有诞生和死亡
  • 任意线程都可以创建其他新的线程

小结

  • 程序是物理存储空间中的数据文件
  • 进程是程序运行后得到的执行实体
  • 线程是进程内部的具体执行单元
  • 一个进程内部可以有多个线程存在
  • 进程是操作系统资源分配的基本单位
  • 线程是操作系统调度执行的基本单位

73.Qt中的多线程编程

Qt中通过 QThread 直接支持多线程

/* QThread中的关键成员函数
 *   void run()		//线程体函数,用于定义线程功能
 *   void start()	//启动函数,将线程入口地址设置为 run 函数
 *   void terminate()	//强制结束线程(不推荐)
 * 如何优雅的结束线程?
 *   run() 函数执行结束是优雅终止线程的唯一方式
 *   在线程类中增加标志变量 m_toStop (volatile bool)
 *   通过 m_toStop 的值判断是否需要从 run() 函数返回
 */
#include <QtCore/QCoreApplication>
#include <QThread>
#include <QDebug>
class Sample : public QThread	//创建线程类
{
protected:
    volatile bool m_toStop;		//标志变量
    void run()	//线程入口函数
    {
        qDebug() << objectName() << " : begin";
        int* p = new int[10000];
        for(int i=0; !m_toStop && (i<10); i++)	//延时并判断标志变量
        {
            qDebug() << objectName() << " : " << i;
            p[i] = i * i * i;
            msleep(500);
        }
        delete[] p;
        qDebug() << objectName() << " : end";
   }
public:
    Sample()
    {
        m_toStop = false;
    }
    void stop()
    {
        m_toStop = true;
    }
};
int main(int argc, char *argv[])	//主线程入口函数
{
    QCoreApplication a(argc, argv);
    qDebug() << "main begin";

    Sample t;	//创建子线程
    t.setObjectName("t");
    t.start();	//启动子线程
    for(int i=0; i<100000; i++)
    {
        for(int j=0; j<10000; j++)
        {

        }
    }
    t.stop();
    //t.terminate();	//强制结束线程(不推荐)

    qDebug() << "main end";
    
    return a.exec();
}

74.多线程间的同步

/* bool QThread::wait(unsigned long time = ULONG_MAX) */
	qDebug() << "begin";
	QThread t;
	t.start();
	t.wait();	//等待子线程执行结束
	qDebug() << "end";

75.多线程间的互斥(上)

QMutex 类是一把线程锁,保证线程间的互斥

/* QMutex 中的关键成员函数
 * void lock()
 *    当锁空闲时,获取锁并继续执行
 *    当锁被获取,阻塞并等待锁释放
 * void unlock()
 *   释放锁(同一把锁的获取和释放锁必须在同一线程中成对出现)
 * 注意:如果 mutex 在调用 unlock() 时处于空闲状态,那么程序的行为是未定义的!
 */
//生产者和消费者
#include <QtCore/QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QDebug>

static QMutex g_mutex;
static QString g_store;
class Producer : public QThread
{
protected:
    void run()
    {
        int count = 0;
        while(true)
        {
            g_mutex.lock();
            //do something with critical resource
            g_store.append(QString::number((count++) % 10));
            qDebug() << objectName() << " : " + g_store;
            g_mutex.unlock();
            msleep(1);
        }
    }
};
class Customer : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_mutex.lock();
            if( g_store != "" )
            {
                g_store.remove(0, 1);
                qDebug() << objectName() << " : " + g_store;
            }
            g_mutex.unlock();
            msleep(1);
        }
    }
};
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    Producer p;
    Customer c;
    p.setObjectName("Producer");
    c.setObjectName("Customer");    
    p.start();
    c.start();

    return a.exec();
}

76.多线程间的互斥(下)

线程的死锁概念

  • 线程间相互等待临界资源而造成彼此无法继续执行

发生死锁的条件

  • 系统中存在多个临界资源且临界资源不可抢占
  • 线程需要多个临界资源才能继续执行

死锁的避免

  • 对所有的临界资源都分配一个唯一的序号(r1,r2,......,rn)
  • 对应的线程锁也分配同样的序号(m1,m2,......,mn)
  • 系统中的每个线程按照严格递增的次序请求资源

信号量

  • 信号量是特殊的线程锁
  • 信号量允许N个线程同时访问临界资源
  • Qt中直接支持信号量(QSemaphore)
/* QSemaphore 对象中维护了一个整形值
 * acquire()使得该值减一,release()使得该值加一
 * 当该值为 0 时, acquire() 函数将阻塞当前线程
 */
QSemaphore sem(1);
sem.acquire();
//do something with critical resource
sem.release();

/* 等价如下使用线程锁 */

QMutex mutex;
mutex.lock();
//do something with critical resource
mutex.unlock();

多个生产者消费者,使用信号量实现高并发

#include <QtCore/QCoreApplication>
#include <QThread>
#include <QSemaphore>
#include <Qdebug>

const int SIZE = 5;
unsigned char g_buff[SIZE] = {0};
QSemaphore g_sem_free(SIZE);
QSemaphore g_sem_used(0);

class Producer : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            int value = qrand() % 256;

            g_sem_free.acquire();

            for(int i=0; i<SIZE; i++)
            {
                if( !g_buff[i] )
                {
                    g_buff[i] = value;

                    qDebug() << objectName() << " generate: {" << i << ", " << value << "}";

                    break;
                }
            }

            g_sem_used.release();

            sleep(2);
        }
    }
};

class Customer : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_sem_used.acquire();

            for(int i=0; i<SIZE; i++)
            {
                if( g_buff[i] )
                {
                    int value = g_buff[i];

                    g_buff[i] = 0;

                    qDebug() << objectName() << " consume: {" << i << ", " << value << "}";

                    break;
                }
            }

            g_sem_free.release();

            sleep(1);
        }
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Producer p1;
    Producer p2;
    Producer p3;

    p1.setObjectName("p1");
    p2.setObjectName("p2");
    p3.setObjectName("p3");

    Customer c1;
    Customer c2;

    c1.setObjectName("c1");
    c2.setObjectName("c2");

    p1.start();
    p2.start();
    p3.start();

    c1.start();
    c2.start();
    
    return a.exec();
}

77.银行家算法的分析与实现

算法策略

  • 将资金优先借予资金需求较少的客户

解决的问题

  • 保证资源分配的安全性

应用场景

  • 操作系统内核中的进程管理
  • 数据库内核中的频繁事务管理

Qt中的算法实现方案

  • 使用多线程机制模拟客户和银行
  • 银行优先分配资源给最小需求的客户
  • 当客户的资源需求无法满足的时候
    • 收回已分配的资源
    • 结束线程

程序见代码文件

78.多线程中的信号与槽(上)

QThread类拥有发射信号和定义槽函数的能力

关键信号:

void started()		//线程开始运行时发射该信号
void finished()		//线程完成运行时发射该信号
void terminated()	//线程被异常终止时发射该信号

如果程序中有多个线程,槽函数是在哪个线程中执行的?

  • 进程中存在栈空间的概念(区别于栈数据结构)
  • 栈空间专用于函数调用(保存函数参数,局部变量等)
  • 线程拥有独立的栈空间(可调用其他函数)
  • 结论:只要函数体中没有访问临界资源的代码,同一个函数可以被多个线程同时调用,且不会产生任何副作用

操作系统通过整形标识管理进程和线程

  • 进程拥有全局唯一的ID值(PID)
  • 线程有进程内唯一的ID值(TID)

QThread中的关键静态成员函数

QThread* currentThread()
Qt::HANDLE currentThreadId()

main.cpp

#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "TestThread.h"
#include "MyObject.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();	//线程ID

    TestThread t;
    MyObject m;

    QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()));
    QObject::connect(&t, SIGNAL(finished()), &m, SLOT(getFinished()));
    QObject::connect(&t, SIGNAL(terminated()), &m, SLOT(getTerminated()));

    t.start();
    
    return a.exec();
}

MyObject.h

#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>

class MyObject : public QObject
{
    Q_OBJECT
public:
    explicit MyObject(QObject *parent = 0);
    
signals:
    
protected slots:
    void getStarted();
    void getFinished();
    void getTerminated();
};

#endif // MYOBJECT_H

MyObject.cpp

#include "MyObject.h"
#include <QThread>
#include <QDebug>

MyObject::MyObject(QObject *parent) :
    QObject(parent)
{
}

void MyObject::getStarted()
{
    qDebug() << "void MyObject::getStarted() tid = " << QThread::currentThreadId();
}

void MyObject::getFinished()
{
    qDebug() << "void MyObject::getFinished() tid = " << QThread::currentThreadId();
}

void MyObject::getTerminated()
{
    qDebug() << "void MyObject::getTerminated() tid = " << QThread::currentThreadId();
}

TestThread.h

#ifndef TESTTHREAD_H
#define TESTTHREAD_H

#include <QThread>

class TestThread : public QThread
{
    Q_OBJECT

protected:
    void run();
public:
    explicit TestThread(QObject *parent = 0);
    
signals:
    void testSignal();
protected slots:
    void testSlot();
};

#endif // TESTTHREAD_H

TestThread.cpp:建立一个线程,发送自定义信号

#include "TestThread.h"
#include <QDebug>

TestThread::TestThread(QObject *parent) :
    QThread(parent)
{
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot()));
}

void TestThread::run()
{
    qDebug() << "void TestThread::run() -- begin tid = " << currentThreadId();

    for(int i=0; i<10; i++)
    {
        qDebug() << "void TestThread::run() i = " << i;

        sleep(1);
    }

    emit testSignal();	//发送信号(用关键字 emit 后面加上要发的信号)

    qDebug() << "void TestThread::run() -- end";
}

void TestThread::testSlot()
{
    qDebug() << "void TestThread::testSlot() tid = " << currentThreadId();
}

79.多线程中的信号与槽(中)

令人不解的问题:当槽函数是线程类中的成员时,为什么依然不在本线程内被调用执行?

隐藏的问题

  • 对象依附于哪一个线程?
  • 对象的依附性与槽函数执行的关系?
  • 对象的依附性是否可以改变?

对象依附于哪一个线程?

  • 默认情况下,对象依附于自身被创建的线程;例如:对象在主线程( main()函数 )中被创建,则依附于主线程

对象的依附性与槽函数执行的关系?

  • 默认情况下,槽函数在其所依附的线程中被调用执行!

对象的依附性是否可以改变?

  • QObject::moveToThread 用于改变对象的线程依附性,使得对象的槽函数在依附的线程中被调用执行
int main(int argc, char *argv[])
{
	TestThread t;
    MyObject m;
    /* 改变对象 m 的线程依附性,使其依附于线程 t */
    m.moveToThread(&t);
}

改变依附性后,对象m的槽函数为什么没有被调用执行?

线程中的事件循环

  • 信号与槽的机制需要事件循环的支持
  • QThread 类中提供的 exec() 函数用于开启线程的事件循环
  • 只有事件循环开启,槽函数才能在信号发送后被调用

研究槽函数的具体执行线程有什么意义?

  • 当信号的发送与对应槽函数的执行在不同线程中时,可能产生临界资源的竞争问题!

更改TestThread.cpp

#include "TestThread.h"
#include <QDebug>

TestThread::TestThread(QObject *parent) :
    QThread(parent)
{
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot()));
}

void TestThread::run()
{
    qDebug() << "void TestThread::run() -- begin tid = " << currentThreadId();

    for(int i=0; i<10; i++)
    {
        qDebug() << "void TestThread::run() i = " << i;

        sleep(1);
    }

    emit testSignal();	//发送信号(用关键字 emit 后面加上要发的信号)
    
    exec();	//开启事件循环

    qDebug() << "void TestThread::run() -- end";
}

void TestThread::testSlot()
{
    qDebug() << "void TestThread::testSlot() tid = " << currentThreadId();
}

80.多线程中的信号与槽(下)

如果线程体函数中开启了事件循环,线程如何正常结束?

QThread::exec() 使得线程进入事件循环

  • 事件循环结束前,exec() 后的语句无法执行
  • quit() 和 exit() 函数用于结束事件循环
  • quit() 等价于 exit(0), exec() 的返回值由 exit() 参数决定

无论事件循环是否开启,信号发送后会直接进入对象所依附线程的事件队列;然而,只有开起了事件循环,对应的槽函数才会在线程中被调用

更改main.cpp

#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "TestThread.h"
#include "MyObject.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();

    TestThread t;
    MyObject m;

    QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()));
    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()));

    m.moveToThread(&t);
    t.moveToThread(&t);

    t.start();

    t.wait(8 * 1000);	//等待8s

    t.quit();		//结束线程的事件循环

    return a.exec();
}

什么时候需要在线程中开启事件循环?

  • 事务性操作( 间断性IO操作、文件操作等 )可以开启线程的事件循环;每次操作通过发送信号的方式使得槽函数在子线程中执行。

文件缓冲区

  • 默认情况下,文件操作时会开辟一段内存作为缓冲区
  • 向文件中写入的数据会先进入缓冲区
  • 只有当缓冲区满或者遇见换行符才将数据写入磁盘
  • 缓冲区的意义在于,减少磁盘的低级IO操作,提高文件读写效率!

Qt线程的使用模式

  • 无事件循环模式
    • 后台执行长时间的耗时任务:文件复制,网络数据读取等
  • 开启事件循环模式
    • 执行事务性操作:文件写入,数据库写入等

main.cpp

#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "FileWriter.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();

    FileWriter writer("C:/Users/hp/Desktop/test.txt");

    if( writer.open() )
    {
        writer.write("D.T.Software\r\n");
        writer.write("ÖÐÎIJâÊÔ\r\n");
        writer.write("µÒÌ©Èí¼þ\r\n");
        writer.close();
    }

    return a.exec();
}

FileWriter.h

#ifndef FILEWRITER_H
#define FILEWRITER_H

#include <QObject>
#include <QFile>
#include <QThread>

class FileWriter : public QObject
{
    Q_OBJECT

    class Worker : public QThread
    {
    protected:
        void run();
    };

    QFile m_file;
    Worker m_worker;
public:
    explicit FileWriter(QString file, QObject *parent = 0);
    bool open();
    void write(QString text);
    void close();
    ~FileWriter();
signals:
    void doWrite(QString text);
    void doClose();
protected slots:
    void writeSlot(QString text);
    void closeSlot();
};

#endif // FILEWRITER_H

FileWriter.cpp

#include "FileWriter.h"
#include <QDebug>

void FileWriter::Worker::run()
{
    qDebug() << "void FileWriter::Worker::run() - begin: tid = " << currentThreadId();

    exec();

    qDebug() << "void FileWriter::Worker::run() - end";
}

FileWriter::FileWriter(QString file, QObject *parent) :
    QObject(parent), m_file(file)
{
    connect(this, SIGNAL(doWrite(QString)), this, SLOT(writeSlot(QString)));
    connect(this, SIGNAL(doClose()), this, SLOT(closeSlot()));

    moveToThread(&m_worker);

    m_worker.start();
}

bool FileWriter::open()
{
    return m_file.open(QIODevice::WriteOnly | QIODevice::Text);
}

void FileWriter::write(QString text)
{
    qDebug() << "void FileWriter::write(QString text) tid = " << QThread::currentThreadId();

    emit doWrite(text);
}

void FileWriter::close()
{
    qDebug() << "void FileWriter::close() tid = " << QThread::currentThreadId();

    emit doClose();
}

void FileWriter::writeSlot(QString text)
{
    qDebug() << "void FileWriter::writeSlot(QString text) tid = " << QThread::currentThreadId();

    m_file.write(text.toAscii());
    m_file.flush();
}

void FileWriter::closeSlot()
{
    qDebug() << "void FileWriter::closeSlot() tid = " << QThread::currentThreadId();

    m_file.close();
}

FileWriter::~FileWriter()
{
    m_worker.quit();
}

81.信号与槽的连接方式

深入信号与槽的连接方式

Qt::DirectConnection	//立即调用
Qt::QueuedConnection	//异步调用
Qt::BlockingQueuedConnection	//同步调用
Qt::AutoConnection		//默认连接
Qt::UniqueConnection	//单一连接
    
bool connect(const QObject* sender,
             const char* signal,
             const QObject* receiver,
             const char* method,
             Qt::ConnectionType type = Qt::AutoConnection)

知识回顾:

  • 每一个线程都有自己的事件队列

  • 线程通过事件队列接收信号

  • 信号在事件循环中被处理

  • 信号进入接收者对象所依附线程的事件队列

Qt::DirectConnection //立即调用

  • 直接在发送信号的线程中调用槽函数,等价于槽函数的实时调用!

Qt::QueuedConnection //异步调用

  • 信号发送至目标线程的事件队列,由目标线程处理;当前线程继续向下执行!

Qt::BlockingQueuedConnection //同步调用

  • 信号发送至目标线程的事件队列,由目标线程处理;当前线程等待槽函数返回,之后继续向下执行!注意:目标线程和当前线程必须不同

Qt::AutoConnection //默认连接

  • 当发送线程 = 接收线程时,采用DirectConnection
  • 当发送线程 != 接收线程时,采用QueuedConnection

Qt::UniqueConnection //单一连接

  • 功能与 AutoConnection 相同,自动确定连接类型
  • 同一个信号与同一个槽函数之间只有一个连接

默认情况下,同一个信号可以多次连接到同一个槽函数

多次连接意味着同一个槽函数的多次调用

82.线程的生命期问题

QThread对象的生命周期与对应的线程生命周期是否一致?

  • 工程实践中的经验准则:线程对象生命周期 > 对应的线程生命周期
//main中的局部函数创建线程,局部函数结束后线程对象就被销毁了,此处是违规的
void test()
{
	MyThread t;
	t.start();
}
//线程的定义
class MyThread : public QThread 
{
protected:
	int i;
	void run() {
		this->i = 1;
		for(int i=0; i<5; i++) {
			this->i *= 2;
			sleep(1);
		}
	}
}

同步型线程设计

  • 概念
    • 线程对象主动等待线程生命期结束后才销毁
  • 特点
    • 同时支持在栈和堆中创建线程对象
    • 对象销毁时确保线程生命期结束
  • 要点
    • 在析构函数中先调用 wait() 函数,强制等到线程运行结束
  • 使用场合
    • 线程生命期相对较短的情形
void sync_thread()
{
    SyncThread st;
    st.start();
}

SyncThread::~SyncThread()
{
    wait();
	//do something to release resource
}

异步型线程设计

  • 概念
    • 线程生命期结束时通知销毁线程对象
  • 特点
    • 只能在堆中创建线程对象
    • 线程对象不能被外界主动销毁
  • 要点
    • 在run()中最后调用deleteLater()函数
    • 线程体函数主动申请销毁线程对象
  • 使用场合
    • 线程生命期不可控,需要长时间运行于后台的情形
void async_thread()
{
    AsyncThread* at = AsyncThread::NewInstance();
    at->start();
}
void AsyncThread::run()
{
    qDebug() << "void AsyncThread::run() tid = " << currentThreadId();

    for(int i=0; i<3; i++)
    {
        //do something complicated
    }
    //apply to destory thread object
    deleteLater();
}

83.另一种创建线程的方式

面向对象程序设计实践的早期,工程中习惯于通过继承的方式扩展系统的功能

class QThread : public Qt
{
protected:
	virtual void run() = 0;
}

现代软件架构技术

  • 尽量使用组合的方式实现系统功能
  • 代码中仅体现需求中的继承关系

通过继承的方式实现新的线程类有什么实际意义?

  • 通过继承的方式实现多线程没有任何实际意义
  • QThread对应于操作系统中的线程
  • QThread用于充当一个线程操作的集合
  • 应该提供灵活的方式制定线程入口函数
  • 尽量避免重写 void run()

QThread 类的改进

class QThread : public QObject
{
	Q_OBJECT
protected:
	virtual void run()
	{
		(void)exec();
	}
}

如何灵活的指定一个线程对象的线程入口函数?

解决方案-信号与槽

  • 在类中定义一个槽函数 void tmain() 作为线程入口函数
  • 在类中定义一个 QThread 成员对象 m_thread
  • 改变当前对象的线程依附性到 m_thread
  • 连接 m_thread 的 start() 信号到 tmain()

main.cpp

#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "AnotherThread.h"

void test()
{
    AnotherThread at;

    at.start();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();

    test();
    
    return a.exec();
}

AnotherThread.h

#ifndef ANOTHERTHREAD_H
#define ANOTHERTHREAD_H

#include <QObject>
#include <QThread>

class AnotherThread : public QObject
{
    Q_OBJECT

    QThread m_thread;
protected slots:
    void tmain();
public:
    explicit AnotherThread(QObject *parent = 0);
    void start();
    void terminate();
    void exit(int c);
    ~AnotherThread();
    
};

#endif // ANOTHERTHREAD_H

AnotherThread.cpp

#include "AnotherThread.h"
#include <QDebug>

AnotherThread::AnotherThread(QObject *parent) :
    QObject(parent)
{
    moveToThread(&m_thread);

    connect(&m_thread, SIGNAL(started()), this, SLOT(tmain()));
}

void AnotherThread::tmain()
{
    qDebug() << "void AnotherThread::tmain() tid = " << QThread::currentThreadId();

    for(int i=0; i<10; i++)
    {
        qDebug() << "void AnotherThread::tmain() i = " << i;
    }

    qDebug() << "void AnotherThread::tmain() end";

    m_thread.quit();	//主动结束线程的事件循环
}

void AnotherThread::start()
{
    m_thread.start();
}

void AnotherThread::terminate()
{
    m_thread.terminate();
}

void AnotherThread::exit(int c)
{
    m_thread.exit(c);
}

AnotherThread::~AnotherThread()
{
    m_thread.wait();
}

84.多线程与界面组件的通信(上)

是否可以在子线程中创建界面组件?

GUI系统设计原则

  • 所有界面组件的操作都只能在主线程中完成;因此,主线程也叫做UI线程!

子线程如何对界面组件进行更新?

解决方案-信号与槽

  • 在子线程类中定义界面组件的更新信号( UpdateUI )
  • 在主窗口类中定义更新界面组件的槽函数( SetInfo)
  • 使用异步方式连接更新信号到槽函数( UpdateUI -> SetInfo)
    • 子线程通过发射信号的方式更新界面组件
    • 所有的界面组件对象只能依附于主线程

main.cpp

#include <QtGui/QApplication>
#include "Widget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    
    return a.exec();
}

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui/QWidget>
#include <QPlainTextEdit>
#include "UpdateThread.h"

class Widget : public QWidget
{
    Q_OBJECT

    UpdateThread m_thread;
    QPlainTextEdit textEdit;
protected slots:
    void appendText(QString text);
public:
    Widget(QWidget *parent = 0);
    ~Widget();
};

#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "TestThread.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    // TestThread* ptt = new TestThread();

    // ptt->start();

    textEdit.setParent(this);
    textEdit.move(20, 20);
    textEdit.resize(200, 150);
    textEdit.setReadOnly(true);

    connect(&m_thread, SIGNAL(updateUI(QString)), this, SLOT(appendText(QString)));

    m_thread.start();
}

void Widget::appendText(QString text)
{
    textEdit.appendPlainText(text);
}

Widget::~Widget()
{
    
}

UpdateThread.h

#ifndef UPDATETHREAD_H
#define UPDATETHREAD_H

#include <QThread>

class UpdateThread : public QThread
{
    Q_OBJECT

protected:
    void run();
public:
    explicit UpdateThread(QObject *parent = 0);
    
signals:
    void updateUI(QString text);
    
};

#endif // UPDATETHREAD_H

UpdateThread.cpp

#include "UpdateThread.h"

UpdateThread::UpdateThread(QObject *parent) :
    QThread(parent)
{
}

void UpdateThread::run()
{
    emit updateUI("Begin");

    for(int i=0; i<10; i++)
    {
        emit updateUI(QString::number(i));

        sleep(1);
    }

    emit updateUI("End");
}

85.多线程与界面组件的通信(下)

子线程能够更改界面组件状态的本质是什么?

  • 子线程发射信号通知主线程界面更新请求;主线程根据具体信号以及信号参数对界面组件进行修改。

是否有其他间接的方式可以让子线程更新界面组件的状态?

解决方案-发送自定义事件

  • 自定义事件类用于描述界面更新细节
  • 在主窗口中重写事件处理函数 event
  • 使用 postEvent 函数(异步方式)发送自定义事件类对象
    • 子线程指定接收消息的对象为主窗口对象
    • 在 event 事件处理函数更新界面状态

main.cpp

#include <QtGui/QApplication>
#include "Widget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    
    return a.exec();
}

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui/QWidget>
#include <QPlainTextEdit>
#include "UpdateThread.h"

class Widget : public QWidget
{
    Q_OBJECT

    UpdateThread m_thread;
    QPlainTextEdit textEdit;

public:
    Widget(QWidget *parent = 0);
    bool event(QEvent *evt);
    ~Widget();
};

#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "StringEvent.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{

    textEdit.setParent(this);
    textEdit.move(20, 20);
    textEdit.resize(200, 150);
    textEdit.setReadOnly(true);

    m_thread.setParent(this);
    m_thread.start();
}

bool Widget::event(QEvent *evt)
{
    bool ret = true;

    if( evt->type() == StringEvent::TYPE )
    {
        StringEvent* se = dynamic_cast<StringEvent*>(evt);

        if( se != NULL )
        {
            textEdit.appendPlainText(se->data());
        }
    }
    else
    {
        ret = QWidget::event(evt);
    }

    return ret;
}

Widget::~Widget()
{
    
}

UpdateThread.h

#ifndef UPDATETHREAD_H
#define UPDATETHREAD_H

#include <QThread>

class UpdateThread : public QThread
{
    Q_OBJECT

protected:
    void run();
public:
    explicit UpdateThread(QObject *parent = 0);
    
};

#endif // UPDATETHREAD_H

UpdateThread.cpp

#include "UpdateThread.h"
#include <QApplication>
#include "StringEvent.h"

UpdateThread::UpdateThread(QObject *parent) :
    QThread(parent)
{
}

void UpdateThread::run()
{
    // emit updateUI("Begin");
    QApplication::postEvent(parent(), new StringEvent("Begin"));

    for(int i=0; i<10; i++)
    {
        // emit updateUI(QString::number(i));
        QApplication::postEvent(parent(), new StringEvent(QString::number(i)));

        sleep(1);
    }

    // emit updateUI("End");
    QApplication::postEvent(parent(), new StringEvent("End"));
}

StringEvent.h

#ifndef _STRINGEVENT_H_
#define _STRINGEVENT_H_

#include <QEvent>
#include <QString>

class StringEvent : public QEvent
{
    QString m_data;
public:
    const static Type TYPE = static_cast<Type>(QEvent::User + 0xFF);

    explicit StringEvent(QString data = "");
    QString data();
    
};

#endif // _STRINGEVENT_H_

StringEvent.cpp

#include "StringEvent.h"

StringEvent::StringEvent(QString data) : QEvent(TYPE)
{
    m_data = data;
}

QString StringEvent::data()
{
    return m_data;
}
posted @ 2020-09-17 19:41  princepeng  阅读(620)  评论(0编辑  收藏  举报