QT中的多线程编程

目录

1、QThread的基本使用

2、多线程间的同步 

3、多线程间的互斥 

3.1、线程锁

3.2、死锁

3.3、信号量

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

4、线程的生命期问题

4.1、线程的生命期问题 

4.2、同步型线程设计

4.3、异步型线程设计 

5、另一种创建线程的方式(组合)


1、QThread的基本使用

Qt中通过QThread直接支持多线程QThread是一个跨平台的多线程解决方案 ,QThread以简洁易用的方式实现多线程编程 

QThread中的关键成员函数 

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

void start() :启动函数,将线程入口地址设置为run函数 

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

#include <QtCore/QCoreApplication>
#include <QThread>
#include <QDebug>

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

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

线程的生命周期 

在工程开发中terminate()是禁止使用的,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();
}

2、多线程间的同步 

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

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

QThread类直接支持线程间的同步 bool QThread::wait(unsigned long time = ULONG_MAX) 

求和的新解法、并行计算

#include <QtCore/QCoreApplication>
#include <QThread>
#include <QDebug>

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

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

3、多线程间的互斥 

3.1、线程锁

临界资源(Critical Resource) :每次只允许一个线程进行访问(读/写)的资源

线程间的互斥(竞争) :多个线程在同一时刻都需要访问临界资源 

QMutex类是一把线程锁,保证线程间的互斥,利用线程锁能够保证临界资源的安全性 

QMutex中的关键成员函数 

  -void lock() 

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

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

  -void unlock() 

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

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

生产消费者问题 

有n个生产者同时制造产品,并把产品存入仓库中 ;有m个消费者同时需要从仓库中取出产品 

规则: 当仓库未满,任意生产者可以存入产品 ;当仓库未空,任意消费者可以取出产品 

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

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

一般性原则 :每一个临界资源都需要一个线程锁进行保护! 

3.2、死锁

死锁示例

#include <QtCore/QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QDebug>

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

线程的死锁概念 :线程间相互等待临界资源而造成彼此无法继续执行 

发生死锁的条件 

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

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

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

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

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

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

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

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

3.3、信号量

信号量是特殊的线程锁 ,信号量允许N个线程同时访问临界资源,Qt中直接支持信号量(QSemaphore) 

QSemaphore使用示例 

QSemaphore对象中维护了一个整型值 ,acquire()使得该值减1 , release()使得该值加1 ,当该值为0时,acquire()函数将阻塞当前线程 

再论生产消费者问题    main.cpp

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

            // 可消费资源数+1
            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;
                }
            }

            // 可生产资源数+1
            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();
}

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

研究一个银行家如何将总数一定的资金,安全地借给若干个顾客,使顾客既能满足对资金的需求,也使银行家可以收回自己的全部资金,不至于破产。 

算法策略 :银行优先分配资源给最小需求的客户

应用场景 :操作系统内核中的进程管理 ;数据库内核中的频繁事务管理 

Qt中的算法实现方案 

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

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

          -当客户的资源需求无法满足的时候 , 收回已分配的资源 ,强制结束线程

银行家算法的实现   main.cpp

#include <QtCore/QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QList>
#include <QDebug>

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 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; 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); // 每次放款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();
}


4、线程的生命期问题

4.1、线程的生命期问题 

C++对象有生命周期; 线程也有生命周期; QThread对象的生命周期对应的线程生命周期是否一致?

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

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

4.2、同步型线程设计

同步型线程设计

    -概念 :线程对象主动等待线程生命期结束后才销毁 

    -特点 :同时支持在栈和堆中创建线程对象 ,对象销毁时确保线程生命期结束 

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

    -使用场合 :线程生命期相对较短的情形

SyncThread.h

#ifndef SYNCTHREAD_H
#define SYNCTHREAD_H

#include <QThread>

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 <QDebug>

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 <QtCore/QCoreApplication>
#include <QDebug>
#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();
}

4.3、异步型线程设计 

异步型线程设计 

    -概念 :线程生命期结束时通知销毁线程对象 

    -特点 :只能在中创建线程对象 ,线程对象不能被外界主动销毁 

    -要点 :在 run() 中最后调用 deleteLater() 函数 ,线程体函数主动申请销毁线程对象 

    -使用场合 :线程生命期不可控,需要长时间运行于后台的情形

AsyncThread.h

#ifndef ASYNCTHREAD_H
#define ASYNCTHREAD_H

#include <QThread>

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 <QDebug>

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 <QtCore/QCoreApplication>
#include <QDebug>
#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();
}


5、另一种创建线程的方式(组合)

历史的痕迹

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

     -早期的Qt版本只能通过继承的方式创建线桯 ,通过继承的方式实现多线程没有任何实际意义,尽量避免重写void run() 

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

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

    //...
} 

现代软件架构技术 

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

                         除了重写的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 <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(); // 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 <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();
}

                                 

  • 44
    点赞
  • 463
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值