目录
1、Qt中的多线程编程
2、多线程间的同步
3、多线程间的互斥
1、线程锁
2、死锁
3、信号量
4、银行家算法的分析与实现
5、线程的生命期问题
1、线程的生命期问题
2、同步型线程设计
3、异步型线程设计
6、另一种创建线程的方式(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();
}
在默认情况下,各个线程独立存在,并行执行
线程的生命周期
重点注意
在工程开发中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();
}
多线程编程的本质
-并发性是多线程编程的本质 ,在宏观上,所有线程并行执行 ,多个线程间相对独立,互不干涉
在特殊情况下,多线程的执行在时序上存在依赖,所以需要控制多线程间的相对执行顺序。
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();
}
若没有wait操作,得到的结果是0(三个子线程还没结束就直接获取结果值)
临界资源(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();
}
一般性原则
每一个临界资源都需要一个线程锁进行保护!
死锁示例
#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();
}
线程的死锁概念
-线程间相互等待临界资源而造成彼此无法继续执行
发生死锁的条件
-系统中存在多个临界资源且临界资源不可抢占(每次只能给一个线程使用)
-线程需要多个临界资源才能继续执行
- 产生死锁有四个必要条件:互斥、请求与保持、不可剥夺、循环等待 (必要条件即满足不一定发生)
死锁的避免 (破坏四个条件)
方法一:资源有序分配法(破坏循环等待)
-对所有的临界资源都分配一个唯一的序号(r1,r2,rn)
-对应的线程锁也分配同样的序号(m1, m2 , mn)
-系统中的每个线程按照严格递增的次序请求资源
信号量的概念
-信号量是特殊的线程锁
-信号量允许N个线程同时访问临界资源
-Qt中直接支持信号量(QSemaphore)
QSemaphore使用示例
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
问题描述
研究一个银行家如何将总数一定的资金,安全地借给若干个顾客,使顾客既能满足对资金的需求,
也使银行家可以收回自己的全部资金,不至于破产。
一些限制条件
-每个顾客在借款前必须提前说明所需资金总额
-每次借钱都是以一个单位进行(如一个单位为1万入民币)
-顾客在拿到一个单位的借款前可能需要等待
-银行保证顾客的等待时间是有限的(借或不借)
算法示例
算法策略
-将资金优先借予资金需求较少的客户
应用场景
-操作系统内核中的进程管理
-数据库内核中的频繁事务管理
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();
}
一个工程中的实际问题
C++对象有生命周期; 线程也有生命周期;
QThread对象的生命周期与对应的线程生命周期是否一致?
工程实践中的经验准则
线程对象生命期必须大于对应线程生命期 ,遵守这个准则可以避免很多问题
下面的代码有问题吗?
局部对象t在start后就会被销毁,同时i也会被销毁,然而线程还在运行,非法访问已经被销毁的变量
同步型线程设计
-概念
★ 线程对象主动等待线程生命期结束后才销毁
-特点
★ 同时支持在栈和堆中创建线程对象
★ 对象销毁时确保线程生命期结束
-要点
★ 在析构函数中先调用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();
}
异步型线程设计
-概念
★ 线程生命期结束时通知销毁线程对象
-特点
★ 只能在堆中创建线程对象
★ 线程对象不能被外界主动销毁
-要点
★ 在 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版本只能通过继承的方式创建线桯
-现代软件技术提倡以组合的方式代替继承
class QThread : public QObject
{
//...
protected:
virtual void run() = 0; // 必须子类实现
//...
}
现代软件架构技术
-参考准则: 尽量使用组合的方式实现系统功能,代码中仅体现需求中的继承关系
除了重写的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();
}