72、进程与线程的概念
值得深思的问题:什么是程序?什么是进程?程序和进程有什么关系?
程序是计算机存储系统中的数据文件:
源代码程序:文本文件,描述程序行为和功能。不能直接运行
可执行程序:二进制文件,直接加载并执行。
源代码程序->编译器->可执行程序(二进制)
进程的概念:有了程序不见得有进程,执行程序得到的
广义概念:程序关于某个数据集合的一次运行活动。
狭义概念:程序被加载到内存中执行后得到进程。
程序和进程的区别:
程序是硬盘中静态的文件:存储系统中的一段二进制表示、
进程是内存中动态的运行实体:数据段,代码段,pc指针,等
程序和进程的联系:
一个程序可能对应多个进程:一个程序多次运行,每次运行产生一个进程。运行多次。
一个进程可能包含多个程序:一个程序依赖多个其它动态库。一个进程由几个可执行程序得到。
值得注意的地方:
在当代操作系统中,资源分配的基本单位是进程,而cpu调度执行的基本单位是线程。
线程的概念:
进程内的执行单元,操作系统中一个可调度的实体,进程中相对独立的一个控制流序列,执行时的现场数据和其他调度所需的信息共同组成线程。线程就是一个执行顺序。
cpu的眼中只有线程。
再论main函数:
c/c++程序被执行后从main函数开始运行,那么这中间经历了什么样的过程?执行流线程
可执行程序->加载执行->1、体统分配资源(内存,io等)2、将pc(寄存器中存储的下一条将要执行的指令)指向main函数入口地址3、从pc指针包含的地址处开始执行(第一个线程)。
在论main函数:
线程是进程使用cpu资源的基本单位。
深入理解进程和线程:
进程中可以存在多个线程共享进程资源。
线程是被调度的执行单元,而进程不是调度单元。
线程不能脱离进程单独存在,只能依赖于进程运行。换句话说:操作系统是基于进程分配资源的,而资源是由线程使用的。隶属的关系。线程是执行流。
线程有生命期,有诞生和死亡。
任意线程都可以创建其它新的线程。
百度云:多线程下载。
多线程编程:在进程中创建多个线程并行执行。
小结:
程序是物理存储空间中的数据文件。进程是程序运行后得到的执行实体。线程是进程内部的具体执行单元。一个进程内部可以有多个线程存在。进程是操作系统资源分配的基本单位。线程是操作系统调度执行的基本单位。线程依赖于进程存在,一个进程内部至少有一个线程。
73、Qt中的多线程编程
Qt中通过QThread直接支持多线程:
QThread是一个跨平台的多线程解决方案。
QThread以简洁易用的方式实现多线程编程。
注意:
1、Qt中的线程以对象的形式被创建和使用。
2、每一个线程对应着一个QThread对象。
QThread中的关键成员函数。
void run()
线程体函数,用于定义线程功能(执行流)。
viod start()
启动函数,将线程入口地址设置为run函数。
void terminate()
强制结束线程(不推荐)
QTread编程示例:
class MyThread:public QThread //创建线程类
{
protected:
void run() //线程入口函数, 重写
{
for(int i=0;i<5;i++)
{ qDebug()<<objectName()<< ":"<<i;
sleep(1); //暂停1秒 ,线程中的成员函数
}}};
进程结束:所有线程结束它才结束。
线程被打断进入不可运行态-线程不会往下执行,只会让出cpu资源,进而系统可以调度其它线程执行。
但是在单cpu的计算机中,多线程之间的并行执行是由操作系统的调度共享cpu资源而得到的。
所以线程的状态中就有一个不可运行的状态。不可运行和运行之间是相互切换的。由操作系统进行这样的切换。
重点注意:
在工程开发中terminate()是禁止使用的! terminate()会使得操作系统暴力终止线程,而不会考虑数据完整性,资源释放等问题。
问题:如何在代码中优雅的终止线程?
解决方案思路:
run()函数执行结束是优雅终止线程的唯一方式。使run函数正常返回。
在线程类中增加标志变量 m_tostop (volatile bool 从内存中读取,不要任意的优化)。
通过m_toStop的值判断是否需要从run()函数返回。
解决方案:
小结:
QThread是一个扩平台的多线程解决方案。
QThread以简洁易用的方式实现多线程编程。
void run()函数用于实现线程执行体。
void start()启动线程并执行run()函数。
工程中禁用void terminate()函数结束线程。
74、多线程间的同步
多线程编程的本质是什么?
并发性是多线程编程的本质。
在宏观上,所有线程并行执行。
多个线程间相对独立,互不干涉。
常规解决方案设计:
问题:线程间总是完全独立毫无依赖的吗?
结论: 在特殊情况下,多线程的执行在时序上存在依赖 !
生活中的例子:
同步的概念:
在特殊情况下,控制多线程间的相对执行顺序。
QThread类直接支持线程间的同步。
bool QThread::wait(unsigned long time=ULONG_MAX)
qDebug()<<"begin";
QThread t;
t.start(); // 创建并启动子线程
t.wait(); // 等待子线程执行结束 run执行完继续向下,可以加个数字,到了就向下执行。
qDebug()<<"end";
小结:
在默认情况下,各个线程独立存在,并行执行。
在特殊情况下,多线程的执行在时序上存在依赖。
QTread类直接支持线程间的同步(wait()成员函数)。
wait()停止当前线程的执行,等待目标线程执行结束。
75、多线程间的互斥
指的思考的问题:
多个线程间除了在时序上可能产生依赖,在其它方面是否也可能产生依赖呢?
生成消费者问题:
有n个生产者同时制造产品,并把产品存入仓库中。
有m个消费者同时需要从仓库中取出产品。
规则:
当仓库未满,任意生产者可以存入产品。
当仓库未空,任意消费者可以取出产品。
生活中的例子-洗手间(WC)
当保洁员进行清洗时,洗手间暂停使用。
生活中的例子--十字路口(Cross Road)
红绿交通灯用于指示,当前十字路口的共享区域是否可用。
临界资源(Critical Resource):每次只允许一个线程进行访问(读/写)的资源。
线程间的互斥(竞争):多个线程在同一时刻都需要访问临界资源。
QMutex类是一把线程锁,保证线程间的互斥:利用线程锁能够保证临界资源的安全性。
QMutex中的关键成员函数:
void lock():
当锁空闲时,获取锁并继续执行。
当锁被获取,阻塞并等待锁释放。
void unlock():
释放锁(同一把锁的获取和释放锁必须在同一线程中成对出现)。
QMutex使用示例:
QMutex mutex;
mutex.lock();
//do something with critical resource 只允许一个线程访问的资源
mutex.unlock();
注意:如果mutex在调用unlock()时处于空闲状态,那么程序的行为是未定义的!
小结:
临界资源每次只允许一个线程进行访问(读/写)。
线程锁(QMutex)用于保护临界资源。
线程只有获取锁之后才能访问临界资源。
锁被其它线程获取时,当前线程进入等待状态。
线程锁的获取和释放必须在同一线程中成对出现。
76、多线程间互斥下
问题:
程序有多少临界资源?需要多少线程锁?
一般性原则:
每一个临界资源都需要一个线程锁进行保护!
有趣的示例:
QMutex m1;
QMutex m2;
线程的死锁概念:
线程间相互等待临界资源而造成彼此无法继续执行。
发生死锁的条件:
系统中存在多个临界资源且临界资源不可抢占。
线程需要多个临界资源才能继续执行。
死锁的避免:
对所有的临界资源都分配一个唯一的序号(r1, r2, ..., rn)
对应的线程锁也分配同样的序号(m1,m2, m3, mn),m1保护r1,m2保护r2....
系统中的每个线程按照严格递增的次序请求资源。
第二个方法:整个线程只使用一把线程锁。将n个临界资源看做一个整体,每个临界资源为它的一部分,不管要访问哪一个临界资源,我们都使用同样的线程锁进行保护。不会死锁但是这样线程的并发性就会降低,系统效率降低。服务器端不能这样,可以使用在客户端程序的编写上边。
信号量的概念:
信号量是特殊的线程锁
信号量允许N个线程同时访问临界资源。(N是自定义的)。
Qt中直接支持信号量(QSemaphore)。
QSemaphore使用示例:
#include <QCoreApplication>
#include <QThread>
#include <QSemaphore>
#include <QDebug>
const int SIZE=5; //定义仓库的个数
unsigned char g_buff[SIZE]={0};
QSemaphore g_sem_free(SIZE); //定义信号量,表示5个仓库空闲
QSemaphore g_sem_used(0); //表示有又多少个位置被使用了
class Producer:public QThread
{
protected:
void run()
{
while(true)
{
int value=qrand()%256; //生产随机数
g_sem_free.acquire(); //尝试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(); //内部整形值加1,有值的话表示消费者可以拿产品了
sleep(2);
}
}
};
class Customer:public QThread
{
protected:
void run()
{
while(true)
{
g_sem_used.acquire();//当前仓库是不是空的,如果有产品,信号量对应的整形值不为0,因此acquire函数不会阻塞
//如果是0就会阻塞在这里
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();//g_sem_free内部整形值加1,当前仓库空了一个出来
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();
}
小结:
多线程间相互等待临界资源将导致死锁。
可以对临界资源进行编号的方法避免死锁。
所有线程必须按照严格递增的次序请求资源。
Qt中直接支持信号量(QSemaphore)
信号量允许N个线程同时访问临界资源。
77、银行家算法的分析与实现
问题描述:
研究一个银行家如何将总数一定的资金,安全的借给若干个顾客,使顾客既能满足对资金的需求,也使银行家可以收回自己的全部资金,不至于破产。
一些限制条件:
每个顾客在借款前必须提前说明所需资金总额。
每次借钱都是以一个单位进行(如:一个单位为1万人民币)。
顾客在拿到一个单位的借款前可能需要等待。
银行保证顾客的等待时间是有限的(借或不借)。
算法示例:
Customer: P,Q,R
Cash: 10
三个顾客共享10个现金单位,三个顾客所需的资金总和为20个单位。
P借8万,有8万才能还钱,Q借3万,有3万才能还钱。R借9万,有9万才能还钱。
算法策略:
将资金优先借给资金需求较少的客户。
应用场景:
操作系统内核中的进程管理。
数据库内核中的频繁事务管理。
Qt中的算法实现方案:
使用多线程机制模拟客户(线程)和银行(线程)。
银行优先分配资源给最小需求的客户。
当客户的资源需求无法满足的时候:收回已分配的资源。强制结束线程。
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>
#include <QList>
class Customer:public QThread
{
protected: //顾客
int m_need;
volatile int m_current;//手上拥有的资金数目
QMutex m_mutex;
void run()
{
bool condition=false;
qDebug()<<objectName()<<"begin to apply money";
do
{
m_mutex.lock();
condition=(m_current<m_need);
m_mutex.unlock();
msleep(10);
}
while(condition);
qDebug()<<objectName()<<"end (get enough money)";
}
public:
Customer(int current,int need)//已经借了多少,所需资金总和
{
m_current=current;
m_need=need;
}
void addMoney(int m)
{
m_mutex.lock();
m_current +=m;
m_mutex.unlock();
}
int backMoney()
{
int ret=0;
m_mutex.lock();
ret=m_current;
m_current=0;
m_mutex.unlock();
return ret;
}
int current()
{
int ret=0;
m_mutex.lock();
ret=m_current;
m_mutex.unlock();
return ret;
}
int need()
{
return m_need;
}
};
class Bank:public QThread
{
protected:
QList<Customer*> m_list; //客户链表
int m_total;//银行资金数量
void run()
{
int index=-1;
qDebug()<<objectName()<<"begin: "<<m_total;
do
{
index=-1;
for(int i=0;i<m_list.count();i++)
{
if(m_list[i]->current()==m_list[i]->need())
{
qDebug()<<objectName()<<"take back money fram: "<<m_list[i]->objectName()<<" "<<m_list[i]->need();
m_total +=m_list[i]->backMoney(); //收回钱
}
}
qDebug()<<objectName()<<"current: "<<m_total;//银行库存
int toGet=0x00FFFFFF;
for(int i=0;i<m_list.count();i++) //找需求最小资金的客户
{
if(m_list[i]->isRunning()) //当前线程还在运行
{
int tmp=m_list[i]->need()-m_list[i]->current();
if(toGet>tmp)
{
index=i;
toGet=tmp;
}
}
}
if( index>=0 )
{
if(toGet<= m_total)
{
qDebug()<<objectName()<<"give money to: "<<m_list[index]->objectName();
m_total--;
m_list[index]->addMoney(1); //每次放一个单位
}
else
{
qDebug()<<objectName()<<"terminate: "<<m_list[index]->objectName();//结束线程也就是这个贷款请求
m_total += m_list[index]->backMoney(); //结束贷款,将资金收回来
m_list[index]->terminate();
}
}
sleep(1);
}
while(index>=0); //大于等于0说明还有客户,小于0说明没客户需要借款了
qDebug()<<objectName()<<"end: "<<m_total;
}
public:
Bank(int total)
{
m_total=total;
}
void addCustomer(Customer* customer)
{
m_list.append(customer);//增加客户
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Customer p(4,8);
Customer q(2,3);
Customer r(2,11);
Bank bank(2);
p.setObjectName("p");
q.setObjectName("q");
r.setObjectName("r");
bank.setObjectName("Bank");
bank.addCustomer(&p);
bank.addCustomer(&q);
bank.addCustomer(&r);
p.start();
q.start();
r.start();
bank.start();
return a.exec();
}
/*
"q" begin to apply money
"r" begin to apply money
"Bank" begin: 2
"p" begin to apply money
"Bank" current: 2
"Bank" give money to: "q"
"q" end (get enough money)
"Bank" take back money fram: "q" 3
"Bank" current: 4
"Bank" give money to: "p"
"Bank" current: 3
"Bank" give money to: "p"
"Bank" current: 2
"Bank" give money to: "p"
"Bank" current: 1
"Bank" give money to: "p"
"p" end (get enough money)
"Bank" take back money fram: "p" 8
"Bank" current: 8
"Bank" give money to: "r"
"Bank" current: 7
"Bank" give money to: "r"
"Bank" current: 6
"Bank" give money to: "r"
"Bank" current: 5
"Bank" give money to: "r"
"Bank" current: 4
"Bank" give money to: "r"
"Bank" current: 3
"Bank" give money to: "r"
"Bank" current: 2
"Bank" give money to: "r"
"r" end (get enough money)
"Bank" take back money fram: "r" 9
"Bank" current: 10
"Bank" end: 10
*/
小结:
银行家算法常用于资源分配的场合。
解决的问题:保证资源分配的安全性。
算法策略:
优先选择需求量较少的客户进行资源分配。
78、所线程中的信号与槽(上)
值得思考的问题:
线程对象是否可以发射信号(signal)?
是否可以定义槽函数(slot)?
QThread类拥有发射信号和定义槽函数的能力、
关键信号:
void started(): 线程开始运行时发射该信号。
void finished(): 线程完成运行时发射该信号。
void terminate(): 线程被异常终止时发射该信号。qt5.95没有这个信号?
让人逃避的问题:
如果程序中有多个线程,槽函数是在哪个线程中执行的?
概念小科普:
进程中存在栈空间的概念(区别与栈数据结构)
栈空间专用与函数调用(保存函数参数,局部变量等)
线程拥有独立的栈空间(可调用其它函数),线程有自己的栈空间。每个线程都有自己的栈空间,互不干扰。
小结论:
只要函数体中没有访问临界资源的代码,同一个函数可以被多个线程同时调用,且不会产生任何副作用。因为每个线程有自己独立的栈空间。所以用的栈空间互不干涉。
槽函数是被哪个线程中调用的呢?
实验前的准备:
操作系统通过整形标识管理进程和线程。
进程拥有全局唯一的ID值(PID)
线程有进程内唯一的ID值(TID)
QThread中的关键静态成员函数:
QThread* currentThread()
Qt::HANDLE currentThreadId() //打印当前线程的ID
//QThread::currentThreadId()
令人不解的问题:
当槽函数是线程类中的成员时,为什么依然不在本线程内被调用执行?
小结:
QThread类拥有发射信号和定义槽函数的能力。
线程在进程内拥有一个唯一的ID值。
线程拥有独立的栈空间用于函数调用。
没有临界资源的函数可以无副作用的被多个线程调用。函数内部没有使用临界资源。
槽函数的调用在某一个线程中完成。
79、多线程的信号与槽中
隐藏的问题:
对象依附于哪一个线程?
对象的依附性与槽函数执行的关系?
对象的依附性是否可以改变?
对象依附于哪个线程?
默认情况下,对象依附于自身被创建的线程;例如:对象在主线程(main()函数)中被创建,则依附于主线程。
int main(int argc,char *argv[])
{
//...
Test Thread t; /* 依附于主线程 */
MyObject m; /* 依附于主线程 */
//。。。
}
对象的依附性与槽函数执行的关系?
默认情况下,槽函数在其依附的线程中被调用执行!
int main(argc,char &argv[])
{
//...
TestThread t; /*依附于主线程 */
MyObject m; /*依附于主线程*/
/* 下面连接中的槽函数都在主线程被调用执行 */
QObject::connect(&t,SIGNAL(started()),&m,SLOT(getStarted()));
QObject::connect(&t,SIGNAL(started()),&m,SLOT(getStarted()));
QObject::connect()
}
对象的依附性是否可以改变?
QObject::moveToThread 用于改变对象的线程依附性,使得对象的槽函数在依附的线程中被调用执行。
int main(int argc,char *argv[])
{
//...
TestThread t; /*依附于主线程*/
MyObject m; /*依附于主线程*/
/* 改变对象m的线程依附性,使其依附于线程t*/
m.moveToThread(&t);
}
问题:
试验中对象m的槽函数为什么没有被执行?
线程中的事件循环:
信号与槽的机制需要事件循环的支持。
QThread类中提供的exec()函数用于开启线程的事件循环。
只有事件循环开启,槽函数才能在信息发送后被调用。
线程的事件循环:
开启了事件循环,才会到事件队列中取信号。取到信号之后就会看看事件有没有关联相关的槽函数。有就调用。这个过程在事件循环中完成的,即exec(),中,你想要槽函数在指定的线程中调用,就要在指定的线程中调用exec成员函数。来开启事件循环。Qt5.95似乎不用这样了。
小结论:
前提条件:对象依附的线程开启了事件循环。
后置结果:对象中的槽函数在依附的线程中被调用执行。
研究槽函数的具体执行线程有什么意义? 避免临界资源的竞争问题。
当信号的发送与对应槽函数的执行在不同线性中时,可能产生临界资源的竞争问题。
如果一个线程中改变成员变量的值,在槽函数中也改变该变量的值,那么在两个线程中改变同一个变量就会打架了。
小结:
默认情况下,对象依附于自身被创建的线程。
QObject::moveToThread用于改变对象的线程依附性。
信号与槽的机制需要事件循环的支持。
exec()函数用于开启线程的事件循环。
对象中的槽函数在依附的线程中被调用执行。
80、多线程的信号与槽下
有趣的问题:
如果线程体函数中开启了事件循环,线程如何正常结束?
QThread::exec()使得线程进入事件循环:
事件循环前,exec()后的语句无法执行。
quit()和exit()函数用于结束事件循环。
quit()<===>exit(0), exec()返回值由exit()参数决定。
注意:
无论事件循环是否开启,信号发送后会直接进入对象所依附线程的时间队列。然而,只有开启了时间循环,对应的槽函数才会在线程中被调用。
设计相关的问题:
什么时候需要在线程中开启事件循环?
设计原则:
事务性操作(间断性IO操作,等)可以开启线程的事件循环,每次操作通过发送信号的方式,使得槽函数在子线程中执行。
这样子就不会阻塞主线程的执行流了。
什么是事务性操作:文件操作。
概念小科普-文件缓冲区:
默认情况下,文件操作时会开辟一段内存作为缓冲区。
向文件中写入的数据会先进入缓冲区。
只有当缓冲区满或者遇见换行符才将数据写入磁盘。(关闭文件或者人为的指定,也会将缓冲区的数据写入磁盘)
缓冲区的意义在于,减少磁盘的低级IO操作,提高文件读写效率。
风险:可能会丢数据。
Qt线程的使用模式:
无事件循环模式:后台执行长时间的耗时任务。文件复制,网络数据读取等
开启事件循环模式:执行事务性操作。文件写入,数据库写入等。耗时,重复进行的。开启事件循环的子线程。
小结:
QThread::exec()使得线程进入事件循环。
quit()<==>exit(0), 用于结束线程的事件循环并返回。
事务性操作可以开启线程的事件循环,将操作分摊到子线程。
工程开发中,多数情况不会开启线程的事件循环。
线程多用于执行后台任务或者耗时任务。
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);
信号与槽的连接方式决定槽函数调用时候的相关行为。
知识回顾:
每一个线程都有自己的事件队列。
线程通过事件队列接收信号。
信号在事件循环中被处理。
发送信号到obj对象。这个对象依附于线性2,所以是线程2的事件队列。
线程2开启事件循环。
Qt::DirectConnection(立即调用)
直接在发送信号的线程中调用槽函数,等价于槽函数的实时调用!
2、Qt::QueuedConnection(异步调用)
信号发送至目标线程的事件队列,由目标线程处理,当前线程继续向下执行。
t.testSignal() -->QueuedConnection-->m.testSlot()
考虑对象的依附性,槽函数执行由目标线程决定。
3、Qt::BlockingQueuedConnection (同步调用)
信号发送至目标线程的事件队列,由目标线程处理;当前线程等待槽函数返回,之后继续向下执行!
注意:目标线程和当前线程必须不同。如果相同:程序会出错,当前发送信号的线程永远无法向下执行了。
t.testSignal()-->BolckingQueuedConnection->m.testSlot()
4、Qt::AutoConnection(默认连接)
AutoConnection是connect函数第五个参数的默认值,也是工程中最常用的连接方式。
依据:线程的依附性。
5、UniqueConnection ( 单一连接 )
描述:
功能与AutoConnection相同,自动确定连接类型。
同一个信号与同一个槽函数之间只有一个连接。
小知识:
默认情况下,同一个信号可以多次连接到同一个槽函数。
多次连接意味着同一个槽函数的多次调用。
小结:
信号与槽的连接存在多种方式。
立即调用方式等价于槽函数的实时调用。忽略对象依附性。
默认方式自动确定连接类型。
同步方式中的目标线程和当前线程必须不同。
单一连接方式确保同一个信号与同一个槽函数只有。
82、线程的生命期问题
一个工程中的实际问题:
c++对象有生命周期;
线程也有生命周期;
QThread对象的生命周期与对应的线程生命周期是否一致?
工程实践中的经验准则。
线程对象生命周期 > 对应的线程生命周期
下面代码有问题吗?
程序出错、
解决方案1:
同步型线程设计:
概念:线程对象主动等待线程生命期结束后才销毁。
特定:
同时支持在栈和堆中创建线程对象。
对象销毁时确保线程生命期结束。
要点:
在析构函数中先调用wait()函数,强制等到线程运行结束。
使用场合:
线程生命期相对较短的情形:
wait()函数等待线程结束才返回。
2异步型线程设计:
概念:
线程生命期结束时通知销毁线程对象。
特定:
只能在堆中创建线程对象。
线程对象不能被外界主动销毁。
要点:
在run()中最后调用deleteLate()函数。申请销毁当前对象。
线程体函数主动申请销毁线程对象。
使用场合:
线程生命期不可控,需要长时间运行于后台的情形。
void AsyncThread::run()
{ for(int i=0;i<5;i++)
{ do something}
// apply to destory thread object
deleteLater(); QObjectl类提供的成员函数,销毁调用deleteLater()的对象
}
小结:
线程对象生命期必须大于对应线程生命期。
同步型线程设计--线程生命期较短。
异步型线程设计--线程生命期不可控。
线程类的设计必须适应具体的场合。
没有万能的设计,只有合适的设计。
83、另一种创建线程的方式
历史的痕迹:
注意:面向对象程序设计实践的早期,工程中习惯于通过继承的方式扩展系统的功能。
run是纯虚函数
现代软件架构技术:
参考准则:
尽量使用组合的方式实现系统功能。
代码中仅体现需求中的继承关系。
思考:
通过继承的方式实现新的线程类有什么实际意义?
事实:
结论:
通过继承的方式实现多线程没有任何实际意义。
QThread对应于操作系统中的线程。
QThread用于充当一个线程操作的集合。
应该提供灵活的方式指定线程入口函数。
尽量避免重写void run().
QThread类的改进:
问题:
如何灵活的指定一个线程对象的线程入口函数?
解决方案--信号与槽
1、在类中定义一个槽函数void tmain()作为线程入口函数。
2、在类中定义一个QThread成员对象m_thread。
3、改变当前对象的线程依附性到m_thread。
4、连接m_thread的start()信号到tmain().
小结:
早期的Qt版本只能通过继承的方式创建线程。
现代软件技术提倡以组合的方式代替继承。
QThread应该作为线程的操作集合而使用。调用exec打开了消息循环。
可以通过信号与槽的机制灵活指定线程入口函数。
勘误:
84、多线程与界面组件的通信上
有趣的问题:
是否可以在子线程中创建界面组件?
GUI系统设计原则:
所有界面组件的操作都只能在主线程中完成,因此主线程也叫做UI线程。
思考:
子线程如何对界面组件进行更新?
解决方案:信号与槽
1、在子线程类中定义界面组件的更新信号(updateUI)
2、在主窗口类中定义更新界面组件的槽函数(setInfo)
3、使用异步方式连接更新信号到槽函数(updateUI -> setInfo)
子线程通过发射信号的方式更新界面组件。
所有的界面组件对象只能依附于主线程。
小结:
现代GUI平台只允许在主线程中直接操作界面组件。
Qt中可以借助信号与槽的机制在子线程中操作界面组件。
进行信号与槽的连接时必须采用异步连接的方式。
界面组件对象必须依附于主线程。
85、多线程与界面组件的通信(下)
思考:子线程能够更改界面组件状态的本质是什么?
本质分析:
子线程发射信息通知主线程界面更新请求。
主线程根据具体信号以及信号参数对界面组件进行修改。
进一步的思考:
是否有其他间接的方式可以让子线程更新界面组件的状态?
解决方案---发送自定义事件
1、自定义事件类用于描述界面更新细节。
2、在主窗口类中重写事件处理函数event。
3、使用postEvent函数(异步方式)发送自定义事件类对象。
子线程指定接收信息的对象为主窗口对象。
在event事件处理函数更新界面状态。
子线程类对象抛出事件到应用程序事件队列中,应用程序对象将事件分发出去,到主窗口类对象中,接收到之后调用event事件处理函数来处理。
所有GUI平台都通用的方案:通过发送自定义事件的方式来间接改变界面组件的状态。
小结:
Qt中可以发送自定义事件在子线程中更改界面组件。
必须使用postEvent函数进行事件发送(异步方式)。
发送的事件对象必须在堆上创建。因为使用了postEvent函数。
子线程创建时必须附带目标对象的地址信息。由于postEvent函数参数的需要,必须要知道接收这个发送事件的目标对象是谁,通过将接受事件的对象设置成为子线程的父组件。这样子线程就附带了目标对象的地址信息了。可以在run函数中拿到目标对象是谁了,只要将它作为子线程的父组件对象就可以了。
番外篇:多进程操作。网络编程(TCP,UDP),串口编程,数据库编程,多语言支持。
Qt平台的学习需要重点在以下几个方面:
GUI系统的核心模型和机制是什么?
界面组件间的父子关系有什么意义?
信号与槽是如何使用的?
多线程和界面组件的关系是什么?