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;
}