# 创建线程与互斥锁 范例类: ```c++ #include #include 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:"< QtConcurrent::run(this,&Widget::f1); // 将f1移交到新线程中运行 ``` ## 互斥锁 ​ 互斥锁(Mutex)再Qt里实现为`QMutex`,基类为`QBasicMutex`。Mutex可以确保多个线程按次序进入`临界区`。 ### 互斥限制机制 ​ `QMutex`无需指定临界资源。对`QMutex`的`lock`和`unlock`只能由一个线程进行,其他进程在尝试`lock QMutex`时,若`QMutex`已被加锁,则等待至`QMutex`解锁之后再运行后续代码。 ​ `QMutex`可以指定为`Recursion(迭代)`,这使`QMutex`在一个线程里可以被多次加锁,要解锁的数量要与加锁的次数相同。 ### 代码运行 在没加`QMutex`时,代码运行结果如下: ![1559213426324](assets/1559213426324.png) 对f1加锁而对f2不加锁: ![1559213561419](assets/1559213561419.png) 对f1和f2都加锁: ![1559213623206](assets/1559213623206.png) ​ 可以看到,**仅仅对一个函数加锁没有任何作用,要想实现对临界资源访问的限制,所有访问临界资源的函数都应当在进入临界区时加锁,退出临界区时解锁** ### QMutex成员函数和属性 QMutex的属性如下: ![1559214210299](assets/1559214210299.png) QMutex的所有函数成员如下(包含继承成员): ![1559214165583](assets/1559214165583.png) ​ 该类所有成员函数都是`线程安全`的。 #### 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 *duration*) ​ 若加锁失败或`QMutex`是一个`NonRecursive`将会返回`false`。 ## QMutexLocker QMutexLocker为`lock`和`unlock`提供了便利。 其构造函数接受一个`QMutex*`,并将会在构造函数中对`QMutex`进行`lock`,在析构函数中进行`unlock`。 ### 成员函数 其所有成员函数如下: ![1559215058503](assets/1559215058503.png) 例: ```c++ void Widget::f1() { QMutexLocker locker(&loc); // 构造加锁 cc<<"begin:f1x:"< 线程池保证了内核的充分利用,防止了过度调度。 ### 影响线程池的因素 - 创建太多线程,将会浪费一定的资源,有些线程未被充分使用。 - 销毁太多线程,将导致之后浪费时间再次创建它们。 - 创建线程太慢,将会导致长时间的等待,性能变差。 - 销毁线程太慢,导致其它线程资源饥饿。 ### 线程池的组成 ​ 下面介绍服务器利用线程池的一个实现: 1. 线程池管理器(Thread Pool Manager):用于创建并管理线程池 2. 工作线程(Work Thread): 线程池中线程 3. 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。 4. 任务队列:用于存放没有处理的任务。提供一种缓冲机制。 ### QT中的线程池 ​ QT使用QThreadPool类表示一个线程池实例。其基类为QObject。所有QThreadPool成员函数都是线程安全的。 ### QThreadPool属性 - **activeThreadCount** : const int - **expiryTimeout** : int - **maxThreadCount** : int - **stackSize** : uint - **objectName** : QString (继承自QObject) | 属性名 | 属性作用 | | :---------------: | :--------------------------: | | activeThreadCount | 保存了线程池中活动的线程数量 | | expiryTimeout | 储存了等待线程的过期时间 | | maxThreadCount | 储存了最大线程数目 | | stackSize | 储存了线程池中线程的栈大小 | ### 公有成员函数 ![1559284204992](assets/1559284204992.png) #### 继承公有函数 ![1559284367552](assets/1559284367552.png) ### 静态成员函数 ![1559284274431](assets/1559284274431.png) #### 继承静态成员函数 ![1559284411499](assets/1559284411499.png) ### 继承的槽函数和信号函数 ![1559284458985](assets/1559284458985.png) ### 继承的保护成员函数 ![1559284495295](assets/1559284495295.png) ## 使用线程池 ​ 要使用QThreadPool中的线程,需要将一个QRunbale对象的拥有权移交给QThreadPool对象。向QThreadPool::start()成员函数传入QRunable对象可达到这一目的: ```c++ class HelloWorldTask : public QRunnable { void run() { qDebug() << "Hello world from thread " << QThread::currentThread(); } } HelloWorldTask *hello = new HelloWorldTask(); // QThreadPool取得所有权,并自动删除 hello QThreadPool::globalInstance()->start(hello); ``` --- ## 参考资料 [百度百科:线程池](https://baike.baidu.com/item/线程池/4745661) [Qt 之 QThreadPool 和 QRunnable](https://blog.csdn.net/liang19890820/article/details/52624735)# 同步类 ​ 以下类用于同步线程: 1. QWaitCondition(线程安全) 2. QSemaphore 3. QSystemSemaphore ## QWaitCondition > QWaitCondition所有的函数都是线程安全的。 ​ QWaitCondition是QT对`同步原语中`的`条件变量`的实现。当条件满足时进入临界区。 ​ `QWaitCondition`同一时刻只能由一个线程访问,一个线程的访问会导致其他线程的`QWaitCondition`无法访问,除非调用`wakeOne()`或`wakeAll()`唤醒其他线程中的QWaitCondition对象。 例: ```c++ #include #include #include 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: ```c++ 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<unlock(); }); QThread *thd2=QThread::create([&](){ mmutex1->lock(); for (;i<6;++i) { cc<unlock(); contion1.wakeAll(); }); thd1->start(); thd2->start(); } ``` 输出结果如下: ```shell 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中信号量类一共有三个: 1. QSemaphore 2. QSystemSemaphore 3. QSemaphoreReleaser(异常安全) ### QSemaphore ​ QSemaphore提供了一个资源计数系统,每个QSemaphore对象可以在初始化时指定资源数目,并使用`acquire(int n=0)`来请求n个资源,使用`release(int n=0)`归还n的资源。当请求的资源不足时,线程会被阻塞直至有充足的资源。 例:(来自Qt Doc) ```c++ 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) ```c++ 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代表了一个异步计算的结果**。 先看一个伪代码示例: ```c++ main thread{ Future fu=SubThread(); // fu代表了SubThread()未来计算的结果 do_something(); // 主线程与子线程异步进行 if(SubThread().isFinished()){ // 若子线程任务完成 cout< 由于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的类型也不同,其类型选择标准如下: 1. `QFutrue`用于不包含任何结果的函数。用于没有返回值或不需要结果(只是用来监控异步进度)的函数。 2. `QFuture`用于获取返回值类型为T的异步计算结果。 ### 异步计算结果 要访问QFuture的结果,可以使用resutAt()进行索引,或者使用迭代器进行访问。 对于拥有多个结果的QFuture对象,resultCount()返回结果的数量。 > QFuture将在下一节进行演示# QtConcurrent并行 ​ QtConcurrent是Qt中的一个`命名空间`,位于`concurrent模块`中。QtConcurrent提供可一系列高级API,可以在不使用低级线程原语(互斥锁、自旋锁、信号量和条件变量)的情况下编写多线程程序。 ## 开辟线程 ​ 与QThread::create()创建一个线程并返回线程指针不同。QtConcurrent::run()在独立线程中运行函数并返回异步计算结果。 ### Run() 1. QFuture\ QtConcurrent::run(QThreadPool *pool, Function function, ...) 在线程池pool中运行函数并返回QFuture\。T与函数的返回值相同。 2. QFuture\ QtConcurrent::run(Function function, ...) 在QThreadPool::globalInstance()线程池内运行函数,并返回结果。 - run函数的`function参数`传入函数指针或函数对象,后续的参数传入函数参数。 > QtConcurrent::run()线程不可被QFuture获取进度、取消、暂停。 > > 只能被用于查询runing / finished状态和函数返回值。 ```c++ static int sum100(int a,int b){ int sum{}; for (int i=a;i fu=QtConcurrent::run(sum100,0,100); fu.waitForFinished(); if(fu.isFinished()){ cc<<"fu is Finished"; cc< 函数 返回值 作用 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。 例: ```c++ // 该段代码处于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(); //关闭线程 ``` 运行结果为: ![1559227682087](assets/1559227682087.png) ## 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成员函数 ### 共有成员函数 ![1559231010491](assets/1559231010491.png) ### 共有槽 ![1559231059087](assets/1559231059087.png) ### 静态成员函数 ![1559231094871](assets/1559231094871.png) ### 保护函数 ![1559231133956](assets/1559231133956.png) ### 静态保护函数 ![1559231156980](assets/1559231156980.png) ## QThread的使用 ​ 上面的范例简单地演示了QThread的使用方法,但是QThread的典型使用时使用类继承,通过重写run()函数来实现定制化功能: 例: [QT之QThread(深入理解)](https://blog.csdn.net/liang19890820/article/details/52186626) [如何正确使用QThread](https://blog.csdn.net/liang19890820/article/details/52621645)