创建线程与互斥锁
范例类:
#include <QWidget>
#include <QMutex>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
// f1和f2用于两个线程,用于修改x,y并显示
void f1();
void f2();
private:
int x{1};
int y{2};
QMutex loc;
};
// 构造函数
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// QtConcurrent::run(this,&Widget::f1);
// QtConcurrent::run(this,&Widget::f2);
}
// f1()和f2()的实现
void Widget::f1()
{
// loc.lock(); 加锁
cc<<"begin:f1x:"<<x<<"\t"<<"f1y:"<<y;
++x;
++y;
cc<<"end:f1x:"<<x<<"\t"<<"f1y:"<<y;
// loc.unlock(); 解锁
}
void Widget::f2()
{
// loc.lock();
cc<<"begin:f2x:"<<x<<"\t"<<"f2y:"<<y;
++x;
++y;
cc<<"end:f2x:"<<x<<"\t"<<"f2y:"<<y;
// loc.unlock();
}
创建线程
这里主要是为了讲解互斥锁,创建线程将会以最简单的一种方式创建:
使用QtConcurrent创建线程:
#include <QtConcurrent/QtConcurrent>
QtConcurrent::run(this,&Widget::f1); // 将f1移交到新线程中运行
互斥锁
互斥锁(Mutex)再Qt里实现为QMutex,基类为QBasicMutex。Mutex可以确保多个线程按次序进入临界区。
互斥限制机制
QMutex无需指定临界资源。对QMutex的lock和unlock只能由一个线程进行,其他进程在尝试lock QMutex时,若QMutex已被加锁,则等待至QMutex解锁之后再运行后续代码。
QMutex可以指定为Recursion(迭代),这使QMutex在一个线程里可以被多次加锁,要解锁的数量要与加锁的次数相同。
代码运行
在没加QMutex时,代码运行结果如下:

对f1加锁而对f2不加锁:

对f1和f2都加锁:

可以看到,仅仅对一个函数加锁没有任何作用,要想实现对临界资源访问的限制,所有访问临界资源的函数都应当在进入临界区时加锁,退出临界区时解锁
QMutex成员函数和属性
QMutex的属性如下:

QMutex的所有函数成员如下(包含继承成员):

该类所有成员函数都是线程安全的。
lock()
lock()函数用于对QMutex进行加锁,若QMutex已被加锁,则该线程将被阻塞直至QMutex解锁。
当RecursionMode=Recursive时,QMutex可被一个线程多次lock,要解锁QMutex,必须调用相同次数的unlock。
tryLock(int timeout=0)
若加锁失败将会返回false并等待timeout毫秒。
try_lock()
若加锁失败将会返回false,该函数与tryLock相同,用于兼容标准库。
QMutex::try_lock_for(std::chrono::duration<Rep, Period> duration)
若加锁失败或QMutex是一个NonRecursive将会返回false。
QMutexLocker
QMutexLocker为lock和unlock提供了便利。
其构造函数接受一个QMutex*,并将会在构造函数中对QMutex进行lock,在析构函数中进行unlock。
成员函数
其所有成员函数如下:

例:
void Widget::f1()
{
QMutexLocker locker(&loc); // 构造加锁
cc<<"begin:f1x:"<<x<<"\t"<<"f1y:"<<y;
++x;
++y;
cc<<"end:f1x:"<<x<<"\t"<<"f1y:"<<y;
}// 析构解锁
多线程的常用术语
一、原语
用于操作系统和计算机网络术语。此处指操作系统中的原语:
原语用以指代一组不可分割、必须连续执行的一段程序代码。在多线程里,原语在执行过程中不允许被中断。原语实现由CPU实现,由操作系统进行封装,由库作者提供给程序员进行使用。
原语的分类
1. 请求原语(Req)
用于高层向底层请求某种业务。
2. 证实原语(Cfm)
业务提供层证实某个动作已经完成。
3. 指示原语(Ind)
业务提供层向高层报告一个与特定业务相关的动作。
4. 响应原语(Res)
用于应答,表示来自高层的指示原语已收到。
二、进程控制原语
进程控制原语包括:
进程的建立
进程的撤销
进程的等待
进程的唤醒
三、同步原语
同步原语是建立在操作系统上的,是操作系统对多线程中的线程协同工作、线程间通信、线程同步、临界资源的竞争等问题提出的解决方案。
主要的抽象同步原语抽象模型有:
互斥锁(Mutual)
条件变量(Condition Variable)
信号量(Semaphores)
信箱(MailBox)
事件标志(Event Flag)
自旋锁(SpinLock)
1. 互斥锁(Mutual)
通过设置互斥锁可以控制对临界资源的访问,互斥锁保证了任意时刻最多有一个线程可访问临界资源。
2.条件变量
条件变量需要和互斥锁配合使用。是一种等待同步机制。用于条件变量的线程首先锁定互斥锁,然后循环判断某个变量是否满足。若条件不满足,阻塞线程并释放互斥锁。当其他线程处理条件并使其满足时,由其他线程调用通知函数唤醒该线程。
3. 信号量
信号量用于管理资源。每个信号量都拥有一个计数器用于储存资源的数量(非负整数)。进入临界区的线程会消耗一个信号量,退出临界区的线程会归还一个信号量。
当信号量为零时,线程会被阻塞,进入等待队列,等待信号量大于零后由其他线程唤醒。
4.信箱
信箱由两条消息队列组成,一条用于发送消息,一条用于接收消息。消息具有有限的容量,广泛被用于线程和进程间通讯。
5. 事件标志
事件标志用于表示一个或几个不同类型的事件的状态。物理结构类似与标志寄存器,使用位(bit)来描述事件的状态,事件标志允许进程:
阻塞到所有事件发生为止。
至少有一个事件发生。
直到所有事件发生为止并清除事件标志。
至少有一个事件发生并清除事件标志。
6. 自旋锁(SpinLock)
自旋锁的运行级别低于其他同步原语,用于中断处理和硬件资源共享。自旋锁时一种等待的锁机制,当发生资源冲突时,线程会不断尝试获取锁以进入临界区,而不是进入阻塞。
读写锁
读写锁时一种特殊的自旋锁,其将资源的访问划分为读、写和读写,因而产生了读锁(ReadLock)、写锁(WriteLock)、读写锁(ReadWriteLock)。
读锁保证读,写锁保证写,读写锁保证读写。
简答题
什么是低级通信原语和高级通信原语?
利用信号量实现进程间信息交换的通信原语,即P.V操作.
P原语和V原语是解决进程间同步和互斥的一对低级通信原语。
参考
百度百科:读写锁# 可运行对象与线程池
可运行对象
QT使用QRunnable来提供可运行对象的接口 。可运行对象代表了一个需要被运行任务(task)或一段代码(piece of code)。
QRunable是一个抽象基类,你必须将重写派生类的run接口。线程池可以通过run()接口运行QRunable对象。
QRunable的属性
QRunable只有一个隐含属性:autoDelete(bool),当该值为true时,则对象可以被线程池自动删除。
QRunable成员函数

线程池
线程的频繁创建与销毁会带来很大的性能问题,为了解决线程的调度问题,计算机科学家提出了线程池(Thread Pool)的概念。线程池本身是一个独立的后台线程,其使用队列的方式维护着多个线程。当线程任务执行完毕后,不再进行销毁,而是进入空闲状态,等待线程池为其分配任务。
线程池有固定的大小,若排队的任务过多,则多余任务将会被挂起,等待线程池调度。
线程池保证了内核的充分利用,防止了过度调度。
影响线程池的因素
创建太多线程,将会浪费一定的资源,有些线程未被充分使用。
销毁太多线程,将导致之后浪费时间再次创建它们。
创建线程太慢,将会导致长时间的等待,性能变差。
销毁线程太慢,导致其它线程资源饥饿。
线程池的组成
下面介绍服务器利用线程池的一个实现:
线程池管理器(Thread Pool Manager):用于创建并管理线程池
工作线程(Work Thread): 线程池中线程
任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
任务队列:用于存放没有处理的任务。提供一种缓冲机制。
QT中的线程池
QT使用QThreadPool类表示一个线程池实例。其基类为QObject。所有QThreadPool成员函数都是线程安全的。
QThreadPool属性
activeThreadCount : const int
expiryTimeout : int
maxThreadCount : int
stackSize : uint
objectName : QString (继承自QObject)
| 属性名 | 属性作用 | | :—————: | :————————–: | | activeThreadCount | 保存了线程池中活动的线程数量 | | expiryTimeout | 储存了等待线程的过期时间 | | maxThreadCount | 储存了最大线程数目 | | stackSize | 储存了线程池中线程的栈大小 |
公有成员函数

继承公有函数

静态成员函数

继承静态成员函数

继承的槽函数和信号函数

继承的保护成员函数

使用线程池
要使用QThreadPool中的线程,需要将一个QRunbale对象的拥有权移交给QThreadPool对象。向QThreadPool::start()成员函数传入QRunable对象可达到这一目的:
class HelloWorldTask : public QRunnable
{
void run() {
qDebug() << "Hello world from thread " << QThread::currentThread();
}
}
HelloWorldTask *hello = new HelloWorldTask();
// QThreadPool取得所有权,并自动删除 hello
QThreadPool::globalInstance()->start(hello);
参考资料
Qt 之 QThreadPool 和 QRunnable# 同步类
以下类用于同步线程:
QWaitCondition(线程安全)
QSemaphore
QSystemSemaphore
QWaitCondition
QWaitCondition所有的函数都是线程安全的。
QWaitCondition是QT对同步原语中的条件变量的实现。当条件满足时进入临界区。
QWaitCondition同一时刻只能由一个线程访问,一个线程的访问会导致其他线程的QWaitCondition无法访问,除非调用wakeOne()或wakeAll()唤醒其他线程中的QWaitCondition对象。
例:
#include <QWidget>
#include <QMutex>
#include <QWaitCondition>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
private:
QWaitCondition contion1;
QMutex *mmutex1=new QMutex;
};
//
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
mmutex1->lock();
QThread *thd1=QThread::create([&](){
contion1.wait(mmutex1); //等待其他contion1无其他线程wake
cc<<"contiion aa";
contion1.wakeAll();
});
QThread *thd2=QThread::create([&](){
cc<<"bb";
contion1.wakeAll();// 唤醒contion1
});
thd1->start();
thd2->start();
}
wait函数需要一个QMutex对象指针作为参数,wait会阻塞当前线程并等待其他线程调用QWaitCondition对象的wakeOnw或wakeAll函数。当一个QWaitCondition被wake后,被阻塞的线程得以继续运行。
QWaitConditiond::wait的参数是一个已被加锁的QMutex,在wait期间,QMutex会被自动解锁。
使用不同线程输出1到17
利用wait会将互斥锁解锁的特点,我们可以使用两个线程来输出连续结果:实现设定临界资源i=0,然后在设定两个线程,一个输出0~3,6~17;另一个输出3~6。
对两个线程访问i时设定互斥锁,然后一个输出到3,wait。而另一个线程将获得锁,输出3~6,唤醒WaitContion后第一个线程输出6~17:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
private:
QWaitCondition contion1;
QMutex *mmutex1=new QMutex;
int i=0;
};
//
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QThread *thd1=QThread::create([&](){
mmutex1->lock();
for (;i<17;++i) {
cc<<QThread::currentThreadId()<<i;
if(i==3){
contion1.wait(mmutex1);
}
}
mmutex1->unlock();
});
QThread *thd2=QThread::create([&](){
mmutex1->lock();
for (;i<6;++i) {
cc<<QThread::currentThreadId()<<i;
}
mmutex1->unlock();
contion1.wakeAll();
});
thd1->start();
thd2->start();
}
输出结果如下:
0x208c 0
0x208c 1
0x208c 2
0x208c 3
0x17cc 3
0x17cc 4
0x17cc 5
0x208c 7
0x208c 8
0x208c 9
0x208c 10
0x208c 11
0x208c 12
0x208c 13
0x208c 14
0x208c 15
0x208c 16
信号量
Qt中信号量类一共有三个:
QSemaphore
QSystemSemaphore
QSemaphoreReleaser(异常安全)
QSemaphore
QSemaphore提供了一个资源计数系统,每个QSemaphore对象可以在初始化时指定资源数目,并使用acquire(int n=0)来请求n个资源,使用release(int n=0)归还n的资源。当请求的资源不足时,线程会被阻塞直至有充足的资源。
例:(来自Qt Doc)
QSemaphore sem(5); // sem.available() == 5
sem.acquire(3); // sem.available() == 2
sem.acquire(2); // sem.available() == 0
sem.release(5); // sem.available() == 5
sem.release(5); // sem.available() == 10
sem.tryAcquire(1); // sem.available() == 9, returns true
sem.tryAcquire(250); // sem.available() == 9, returns false
QSemaphoreReleaser
QSemaphoreReleaser为 QSemaphore::release()的调用提供了一个异常安全(exception-safe)
你可以构造一个QSemaphoreReleaser(QSemaphore &sem, int n = 1)对象,当其析构时将自动会自动从sem中释放n个资源。
QSemaphoreReleaser主要是为了防止由于代码执行中出现异常而导致的资源未释放问题。
例如:(Qt Doc)
const QSemaphoreReleaser releaser(sem);
// ... do something that may throw or early return
// implicitly calls sem.release() here and at every other return in between
Future
Future的基本概念
Future,我借用数学术语,将其译为期望。
在多线程编程中,若主线程需要子线程运行的结果,但是主线程又无需等待子线程结束,那么,就可以使用Futrue来代表子线程“未来的结果”。主线程通过检查子线程的进度,来决定对其结果(也就是Future)的使用。
由于子线程与主线程的进度是不同步的,因此称为两者是异步的。Future代表了一个异步计算的结果。
先看一个伪代码示例:
main thread{
Future fu=SubThread(); // fu代表了SubThread()未来计算的结果
do_something(); // 主线程与子线程异步进行
if(SubThread().isFinished()){ // 若子线程任务完成
cout<<fu<<endl; // 打印子线程的结果
}
}
由于Future代表了子线程未来的结果,而子线程的结果应当是我们期望得到的,因此,我将其译为 “期望”。
QFuture
QFuture状态
QFuture不仅代表了一个状态,而且还可以通过QFuture对子线程进行一定控制,QFuture代表的异步子线程有状态,其操作与相应说明如下:
| 状态名 | 进入状态函数 | 确定状态函数 |
|---|---|---|
| 开始 | bool isStarted() | |
| 运行 | bool isRunning() | |
| 暂停 | void pause() | bool isPaused() |
| void setPaused(bool paused) | ||
| void togglePaused() | ||
| 继续 | void resume() | |
| 完成 | void waitForFinished() | bool isFinished() |
| 取消 | void cancel() | bool isCanceled() |
| 结果可被读取 | bool isResultReadyAt(int index) |
异步暂停
QFuture的暂停并不是无条件的,只有满足条件的异步计算才可以被暂停,这一点将会在QtConcurrent中说明。
即使isPauseed()返回true,计算可能仍在进行
QFuture类型
QFuture是一个模板类,根据子线程运行函数的不同,QFuture的类型也不同,其类型选择标准如下:
QFutrue<void>用于不包含任何结果的函数。用于没有返回值或不需要结果(只是用来监控异步进度)的函数。QFuture<T>用于获取返回值类型为T的异步计算结果。
异步计算结果
要访问QFuture的结果,可以使用resutAt()进行索引,或者使用迭代器进行访问。
对于拥有多个结果的QFuture对象,resultCount()返回结果的数量。
QFuture将在下一节进行演示# QtConcurrent并行
QtConcurrent是Qt中的一个命名空间,位于concurrent模块中。QtConcurrent提供可一系列高级API,可以在不使用低级线程原语(互斥锁、自旋锁、信号量和条件变量)的情况下编写多线程程序。
开辟线程
与QThread::create()创建一个线程并返回线程指针不同。QtConcurrent::run()在独立线程中运行函数并返回异步计算结果。
Run()
QFuture<T> QtConcurrent::run(QThreadPool *pool, Function function, …)
在线程池pool中运行函数并返回QFuture<T>。T与函数的返回值相同。
QFuture<T> QtConcurrent::run(Function function, …)
在QThreadPool::globalInstance()线程池内运行函数,并返回结果。
run函数的
function参数传入函数指针或函数对象,后续的参数传入函数参数。
QtConcurrent::run()线程不可被QFuture获取进度、取消、暂停。
只能被用于查询runing / finished状态和函数返回值。
static int sum100(int a,int b){
int sum{};
for (int i=a;i<b;++i) {
sum+=i;
}
return sum;
}
QFuture<int> fu=QtConcurrent::run(sum100,0,100);
fu.waitForFinished();
if(fu.isFinished()){
cc<<"fu is Finished";
cc<<fu.result();
}
运行结果:

Map和Filter
Map和Filter分别用于映射和过滤,Map和Filter族函数用于将指定函数作用于一个序列容器,就地修改或返回一个新值。
| 函数 | 返回值 | 作用 |
|---|---|---|
| map(Sequence &sequence, MapFunctor function) | QFuture<void> | 就地修改序列容器 |
| map(Iterator begin, Iterator end, MapFunctor function) | ||
| mapped(const Sequence &sequence, MapFunctor function) | 被修改后的容器 | 返回一个被修改后的容器,原容器不做改变 |
| mapped(Iterator begin, Iterator end, MapFunctor function) | ||
| mappedReduced(const Sequence &sequence, MapFunctor mapFunction, ReduceFunctor reduceFunction, QtConcurrent::ReduceOptions reduceOptions = ...) |
QFuture<ResultType> | 根据reduceFunction函数对整个序列容器进行运算,返回运算结果 |
| mappedReduced(Iterator begin, Iterator end, MapFunctor mapFunction, ReduceFunctor reduceFunction, QtConcurrent::ReduceOptions reduceOptions = ...) |
||
| filter(Sequence &sequence, KeepFunctor filterFunction) | QFuture<void> | 就地过滤容器 |
| filtered(const Sequence &sequence, KeepFunctor filterFunction) | 被过滤后的容器 | 返回一个被过滤后的容器,原容器不做改变 |
| filtered(Iterator begin, Iterator end, KeepFunctor filterFunction) | ||
| filteredReduced(const Sequence &sequence, KeepFunctor filterFunction, ReduceFunctor reduceFunction, QtConcurrent::ReduceOptions reduceOptions = ...) |
QFuture<ResultType> | 根据reduceFunction函数对整个序列容器进行运算,返回运算结果 |
| filteredReduced(Iterator begin, Iterator end, KeepFunctor filterFunction, ReduceFunctor reduceFunction, QtConcurrent::ReduceOptions reduceOptions = ...) |
BloackMap和BloackFilter
与map和filter类似,但是运行时会阻塞当前线程至运算结束。函数名数量与map和filter完全相同,只需在函数名前加上blocking并转化为驼峰命名法即可,例:
map–>blockingMap
QThread
QThread是线程的实体。每个QThread对象都代表了一个独立的线程。QThread继承自QObject。
例:
// 该段代码处于GUI构造函数中
QThread*thd=new QThread(this); // 创建QThread对象
QObject *obj=new QObject; // 创建QObject对象
obj->moveToThread(thd); //将QObject从当前线程移交到thd线程中
thd->start(); //打开线程
connect(thd,&QThread::destroyed,[](){
qDebug()<<"thread destory";
});
thd->exit(); //关闭线程
运行结果为:

QThread属性
QThread的Priority属性保存了线程的优先级。在无优先级的操作系统中该属性不起作用,优先级共八级,除了最后一级外,等级越高,优先级越高:
| 常量 | 值 | 描述 | | :—————————: | :–: | :————————————: | | QThread::IdlePriority | 0 | 没有其它线程运行时才调度 | | QThread::LowestPriority | 1 | 比LowPriority调度频率低 | | QThread::LowPriority | 2 | 比NormalPriority调度频率低 | | QThread::NormalPriority | 3 | 操作系统的默认优先级 | | QThread::HighPriority | 4 | 比NormalPriority调度频繁 | | QThread::HighestPriority | 5 | 比HighPriority调度频繁 | | QThread::TimeCriticalPriority | 6 | 尽可能频繁的调度 | | QThread::InheritPriority | 7 | 使用和创建线程同样的优先级. 这是默认值 |
QThread成员函数
共有成员函数

共有槽

静态成员函数

保护函数

静态保护函数

QThread的使用
上面的范例简单地演示了QThread的使用方法,但是QThread的典型使用时使用类继承,通过重写run()函数来实现定制化功能:
例: