QT中的多线程编程

目录

1、Qt中的多线程编程 

2、多线程间的同步 

3、多线程间的互斥 

1、线程锁

2、死锁

3、信号量

4、银行家算法的分析与实现

5、线程的生命期问题

1、线程的生命期问题 

2、同步型线程设计

3、异步型线程设计 

6、另一种创建线程的方式(QT推荐)


1、Qt中的多线程编程 

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

     - QThread是一个跨平台的多线程解决方案 

     - QThread以简洁易用的方式实现多线程编程 

 

QThread中的关键成员函数 

     - void run() 

              ★ 线程体函数,用于定义线程功能(执行流) 

     - void start() 

              ★ 启动函数,将线程入口地址设置为run函数 

     - void terminate() 

              ★ 强制结束线程(不推荐),  所有线程都结束后,进程才结束 

编程实验 

main.cpp

#include 
#include 
#include 

class MyThread : public QThread
{
protected:
    void run() // 线程入口函数
    {
        qDebug() << objectName() << " : " << "run() begin";

        for(int i=0; i<5; i++)
        {
            qDebug() << objectName() << " : " << i;

            sleep(1);
        }

        qDebug() << objectName() << " : " << "run() end";
    }
};

int main(int argc, char *argv[]) // 主线程入口函数
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() begin";

    MyThread t; // 创建子线程

    t.setObjectName("t");

    t.start(); // 启动子线程

    MyThread tt;

    tt.setObjectName("tt");

    tt.start();

    for(int i=0; i<100000; i++)
    {
        for(int j=0; j<10000; j++)
        {
            // 延时, 防止主线程将先于子线程结束 
        }
    }

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

QT中的多线程编程_第1张图片

                     在默认情况下,各个线程独立存在,并行执行 

线程的生命周期 

QT中的多线程编程_第2张图片

重点注意 

            在工程开发中terminate()是禁止使用的,terminate()会使得操作系统暴力终止线程,

           而不会考虑数据完整性,资源释放等问题! 

如何在代码中优雅的终止线程? 

解决方案思路 

      -run() 函数执行结束是优雅终止线程的唯一方式 

      -在线程类中增加标志变量 m_toStop (volatile bool) 

      -通过m_toStop的值判断是否需要从run()函数返回 

编程实验 

优雅的线程控制 

main.cpp

#include 
#include 
#include 

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

2、多线程间的同步 

多线程编程的本质

     -并发性是多线程编程的本质 ,在宏观上,所有线程并行执行 ,多个线程间相对独立,互不干涉 

在特殊情况下,多线程的执行在时序上存在依赖,所以需要控制多线程间的相对执行顺序。

QThread类直接支持线程间的同步 

      -bool QThread::wait(unsigned long time = ULONG_MAX) 

编程实验 

求和的新解法、并行计算初探 

#include 
#include 
#include 

/*
  sum(n) => 1 + 2 + 3 + ... + n

  sum(1000) => ?

            [1, 1000] =  [1, 300] [301, 600] [601, 1000]
  */

class Calculator : public QThread
{
protected:
    int m_begin;
    int m_end;
    int m_result;

    void run()
    {
        qDebug() << objectName() << ": run() begin";

        for(int i=m_begin; i<=m_end; i++)
        {
            m_result += i;

            msleep(10);
        }

        qDebug() << objectName() << ": run() end";
    }
public:
    Calculator(int begin, int end)
    {
        m_begin = begin;
        m_end = end;
        m_result = 0;
    }

    void work()
    {
        run();
    }

    int result()
    {
        return m_result;
    }
};

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

    qDebug() << "main begin";

    Calculator cal1(1, 300);
    Calculator cal2(301, 600);
    Calculator cal3(601, 1000);

    cal1.setObjectName("cal1");
    cal2.setObjectName("cal2");
    cal3.setObjectName("cal3");

    // cal1.work();
    // cal2.work();
    // cal3.work();

    cal1.start();
    cal2.start();
    cal3.start();

    cal1.wait(); // 等待子线程执行结束
    cal2.wait();
    cal3.wait();

    int result = cal1.result() + cal2.result() + cal3.result();

    qDebug() << "result = " << result;

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

QT中的多线程编程_第3张图片

若没有wait操作,得到的结果是0(三个子线程还没结束就直接获取结果值)


3、多线程间的互斥 

1、线程锁

临界资源(Critical Resource) 

  -每次只允许一个线程进行访问(读/写)的资源

线程间的互斥(竞争) 

  -多个线程在同一时刻都需要访问临界资源 

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

  -利用线程锁能够保证临界资源的安全性 

QMutex中的关键成员函数 

  -void lock() 

      • 当锁空闲时,获取锁并继续执行 

      • 当锁被获取阻塞并等待锁释放 

  -void unlock() 

      • 释放锁(同一把锁的获取和释放锁必须在同一线程中成对出现) 

      • 注意: 如果 mutex 在调用 unlock() 时处于空闲状态,那么程序的行为是未定义的!

QMutex使用示例 

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

生产消费者问题 

       -有n个生产者同时制造产品,并把产品存入仓库中 

       -有m个消费者同时需要从仓库中取出产品 

       -规则: 

             • 当仓库未满,任意生产者可以存入产品 

             • 当仓库未空,任意消费者可以取出产品 

编程实验 

解决生产消费者者问题   

#include 
#include 
#include 
#include 

static QMutex g_mutex;  // 线程锁
static QString g_store; // 仓库

class Producer : public QThread
{
protected:
    void run()
    {
        int count = 0;

        while(true)
        {
            g_mutex.lock();

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

一般性原则 

       每一个临界资源都需要一个线程锁进行保护! 

2、死锁

死锁示例

#include 
#include 
#include 
#include 

QMutex g_mutex_1;
QMutex g_mutex_2;

class ThreadA : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_mutex_1.lock();

            qDebug() << objectName() << "get m1";

            g_mutex_2.lock();

            qDebug() << objectName() << "get m2";

            qDebug() << objectName() << "do work ...";

            g_mutex_2.unlock();
            g_mutex_1.unlock();

            sleep(1);
        }
    }
};

class ThreadB : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_mutex_2.lock();

            qDebug() << objectName() << "get m2";

            g_mutex_1.lock();

            qDebug() << objectName() << "get m1";

            qDebug() << objectName() << "do work ...";

            g_mutex_1.unlock();
            g_mutex_2.unlock();

            sleep(1);
        }
    }
};

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

    ThreadA ta;
    ThreadB tb;

    ta.setObjectName("ta");
    tb.setObjectName("tb");

    ta.start();
    tb.start();

    return a.exec();
}

QT中的多线程编程_第4张图片

线程的死锁概念 

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

发生死锁的条件 

-系统中存在多个临界资源且临界资源不可抢占(每次只能给一个线程使用)

-线程需要多个临界资源才能继续执行 

 - 产生死锁有四个必要条件:互斥、请求与保持、不可剥夺、循环等待  (必要条件即满足不一定发生)

QT中的多线程编程_第5张图片

 

    死锁的避免 (破坏四个条件)

方法一:资源有序分配法(破坏循环等待)

        -对所有的临界资源都分配一个唯一的序号(r1,r2,rn) 

        -对应的线程锁也分配同样的序号(m1, m2 , mn) 

        -系统中的每个线程按照严格递增的次序请求资源 

3、信号量

信号量的概念 

        -信号量是特殊的线程锁 

        -信号量允许N个线程同时访问临界资源

        -Qt中直接支持信号量(QSemaphore) 

 

QSemaphore使用示例 

QT中的多线程编程_第6张图片

          QSemaphore对象中维护了一个整型值 

                ★ acquire()使得该值减1 , release()使得该值加1 

                ★ 当该值为0时,acquire()函数将阻塞当前线程 

编程实验 

再论生产消费者问题    

main.cpp

#include 
#include 
#include 
#include 

const int SIZE = 5;
unsigned char g_buff[SIZE] = {0}; // 5个仓库

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信号量(整形值)为0,阻塞在这里
            g_sem_free.acquire();

            for(int i=0; i

4、银行家算法的分析与实现

问题描述 

       研究一个银行家如何将总数一定的资金,安全地借给若干个顾客,使顾客既能满足对资金的需求,

       也使银行家可以收回自己的全部资金,不至于破产。 

一些限制条件 

       -每个顾客在借款前必须提前说明所需资金总额 

       -每次借钱都是以一个单位进行(如一个单位为1万入民币)

       -顾客在拿到一个单位的借款前可能需要等待 

       -银行保证顾客的等待时间是有限的(借或不借) 

算法示例 

QT中的多线程编程_第7张图片

 

算法策略 

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

应用场景 

          -操作系统内核中的进程管理 

          -数据库内核中的频繁事务管理 

 

Qt中的算法实现方案 

          -使用多线程机制模拟客户和银行 

          -银行优先分配资源给最小需求的客户

          -当客户的资源需求无法满足的时候 

                     收回已分配的资源 

                     强制结束线程

编程实验 

银行家算法的实现   main.cpp

#include 
#include 
#include 
#include 
#include 

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 m_list;  // 等待放款的客户
    int m_total;              // 银行库存资金数

    void run()
    {
        int index = -1;

        qDebug() << objectName() << " begin: " << m_total;

        do
        {
            index = -1;

            // 若顾客资金已满足需求,收回钱
            for(int i=0; icurrent() == m_list[i]->need() )
                {
                    qDebug() << objectName() << " take back money from " << 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; iisRunning() )
                {
                    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); // 每次放款1单位
                }
                else
                {
                    qDebug() << objectName() << " terminate: " << m_list[index]->objectName();

                    m_total += m_list[index]->backMoney();  // 结束贷款,收回钱

                    m_list[index]->terminate();
                }
            }

            sleep(1);
        }
        while( index >= 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();
}

QT中的多线程编程_第8张图片


5、线程的生命期问题

1、线程的生命期问题 

一个工程中的实际问题 

        C++对象有生命周期; 线程也有生命周期; 

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

工程实践中的经验准则 

QT中的多线程编程_第9张图片

                线程对象生命期必须大于对应线程生命期 ,遵守这个准则可以避免很多问题

下面的代码有问题吗? 

QT中的多线程编程_第10张图片

              局部对象t在start后就会被销毁,同时i也会被销毁,然而线程还在运行,非法访问已经被销毁的变量

 

2、同步型线程设计

同步型线程设计

    -概念 

             ★ 线程对象主动等待线程生命期结束后才销毁 

    特点 

             ★ 同时支持在栈和堆中创建线程对象 

             ★ 对象销毁时确保线程生命期结束 

    -要点 

             ★ 在析构函数中先调用wait()函数,强制等到线程运行结束 

    -使用场合 

             ★ 线程生命期相对较短的情形

编程实验 

SyncThread.h

#ifndef SYNCTHREAD_H
#define SYNCTHREAD_H

#include 

class SyncThread : public QThread
{
    Q_OBJECT

protected:
    void run();

public:
    explicit SyncThread(QObject *parent = 0);
    ~SyncThread();
};

#endif // SYNCTHREAD_H

SyncThread.cpp

#include "SyncThread.h"
#include 

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

void SyncThread::run()
{
    qDebug() << "void SyncThread::run() tid = " << currentThreadId();

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

        sleep(1);
    }

    qDebug() << "void SyncThread::run() end";
}

SyncThread::~SyncThread()
{
    wait(); // 等待线程结束才析构

    qDebug() << "SyncThread::~SyncThread() destroy thread object";
}

main.cpp

#include 
#include 
#include "SyncThread.h"

void sync_thread()
{
    SyncThread st;

    st.start();
}

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

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

    sync_thread();

    return a.exec();
}

QT中的多线程编程_第11张图片

3、异步型线程设计 

异步型线程设计 

    -概念 

             ★ 线程生命期结束时通知销毁线程对象 

    -特点 

             ★ 只能在中创建线程对象 

             ★ 线程对象不能被外界主动销毁 

    -要点 

             ★ 在 run() 中最后调用 deleteLater() 函数 

             ★ 线程体函数主动申请销毁线程对象 

    -使用场合 

             ★ 线程生命期不可控,需要长时间运行于后台的情形

编程实验

AsyncThread.h

#ifndef ASYNCTHREAD_H
#define ASYNCTHREAD_H

#include 

class AsyncThread : public QThread
{
    Q_OBJECT

protected:
    void run();
    explicit AsyncThread(QObject *parent = 0); // 只能在堆空间创建对象
    ~AsyncThread(); // 禁用delete

public:
    static AsyncThread* NewInstance(QObject *parent = 0);
    
};

#endif // ASYNCTHREAD_H

AsyncThread.cpp

#include "AsyncThread.h"
#include 

AsyncThread* AsyncThread::NewInstance(QObject *parent)
{
    return new AsyncThread(parent);
}

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

void AsyncThread::run()
{
    qDebug() << "void AsyncThread::run() tid = " << currentThreadId();

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

        sleep(1);
    }

    qDebug() << "void AsyncThread::run() end";

    deleteLater(); // 通知销毁当前线程对象 void QObject::deleteLater () [slot]
}

AsyncThread::~AsyncThread()
{
    qDebug() << "AsyncThread::~AsyncThread() destroy thread object";
}

main.cpp

#include 
#include 
#include "AsyncThread.h"

void async_thread()
{
    AsyncThread* at = AsyncThread::NewInstance();

    at->start();
}

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

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

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

QT中的多线程编程_第12张图片


6、另一种创建线程的方式(QT推荐)

历史的痕迹

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

     -早期的Qt版本只能通过继承的方式创建线桯 

     -现代软件技术提倡以组合的方式代替继承 

class QThread : public QObject
{
    //...
    
protected: 
    virtual void run() = 0;  // 必须子类实现

    //...
} 

现代软件架构技术 

     -参考准则: 尽量使用组合的方式实现系统功能,代码中仅体现需求中的继承关系 

QT中的多线程编程_第13张图片

                         除了重写的run不同,其它接口完全相同

结论

     -通过继承的方式实现多线程没有任何实际意义

     -QThread 对应于操作系统中的线程 

     -QThread 用于充当一个线程操作的集合 

     -应该提供灵活的方式指定线程入口函数 

     -尽量避免重写void run() 

QThread类的改进 

class QThread : public QObject
{
    //...
    
protected: 
    virtual void run() // 新版本QT
    {
        (void)exec();  // 默认开启事件循环
    }
       
    //...
} 

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

 

解决方案-信号与槽 

       1. 在类中定义一个槽函数 void tmain() 作为线程入口函数 

       2. 在类中定义一个 QThread 成员对象 m_thread 

       3. 改变当前对象的线程依附性到 m_thread 

       4 连接 m_thread 的 start() 信号到 tmain() 

编程实验 

AnotherThread.h

#ifndef ANOTHERTHREAD_H
#define ANOTHERTHREAD_H

#include 
#include 

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 

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(); // m_thread线程开启,定义的tmain函数被调用(run函数也被调用,开启事件循环)
}

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

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

AnotherThread::~AnotherThread()
{
    m_thread.wait(); // 同步设计
}

main.cpp

#include 
#include 
#include 
#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();
}

QT中的多线程编程_第14张图片

                                 

你可能感兴趣的:(QT【笔记】)