目录
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 + ... + nsum(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;}}// 可消费资源数+1g_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;}}// 可生产资源数+1g_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_OBJECTprotected: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_OBJECTprotected:void run();explicit AsyncThread(QObject *parent = 0); // 只能在堆空间创建对象~AsyncThread(); // 禁用deletepublic: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_OBJECTQThread 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();
}