72、进程与线程

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系统的核心模型和机制是什么?

界面组件间的父子关系有什么意义?

信号与槽是如何使用的?

多线程和界面组件的关系是什么?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值