目录
前言:目前项目中使用到QSemaphore来构建线程池,之前只是简单使用QThread类,这里系统记录多线程的基本使用,方便后面使用到的时候翻开回忆,不再到处搜索知识点。
线程:进程是cpu、内存等资源占用的基本单位,它是应用程序实例(线程)要使用资源的集合。如果一个App没有创建多线程,那么它只有一个线程即主线程。进程之间相互独立,线程之间共享进程资源,通信便利,进程是线程的运行环境。线程有5种状态分别是创建建、运行、阻塞(需要线程锁或条件变量)、计时等待和退出。
「创建」一个新线程,等待 CPU 来执行;当 CPU 来执行时,如果该线程需要等待另外某个事件被执行完后才能执行,那该线程此时是处于「阻塞」状态;如果不需要等待其他事件,线程就可以被「运行」了,也可以称为正在占用时间片;时间片用完后,线程会处于「就绪」状态,等待下一次时间片的到来;所有任务都完成后,线程就会进入「退出」状态,操作系统就会释放该线程所分配的资源。
线程同步:所谓协同步调,即多个线程打配合完成某项复杂工作。线程同步涉及的技术有线程锁(互斥对象Mutex),条件变量(condition)和信号量(semaphore)。
Qt中提供了三种在主线程之外创建工作线程的方法:1. 继承QThread;2.继承QObject,然后使用moveToThread(QThread * targetThread)将对象移动到工作线程中执行;3.继承QRunnable,并将创建的对象移动到QThreadPool中进行执行。
Qt官方建议仅仅在需要扩展QThread本身的功能时使用第一种方法,而执行一般的工作时可使用第2种或第3种方法。后两者的区别是第2种方法可以通过信号进行线程间的通信。QRunnable没有继承QObject,适合执行不关心执行进度或者不需要在中间环节进行控制的方法。
QThread 继承 QObject.。它可以发送started和finished信号,也提供了一些slot函数。
QObject 可以用于多线程,可以发送信号调用存在于其他线程的slot函数,也可以postevent给其他线程中的对象。之所以可以这样做,是因为每个线程都有自己的事件循环
1.1 QThread使用方法
QThread 的执行入口函数是run(),从该函数返回将结束线程;wait()函数是等待run()函数执行结束并返回,此时wait()返回值为true;创建线程的方法有两种,1.直接继承QThread; 2.使用QObject::moveToThread()将 QObject 对象移到新开的 QThread 线程对象中,这样 QObject 对象中所有的耗时操作都会在新线程中被执行。
class Worker : public QObject
{
Q_OBJECT
QThread workerThread;
public slots:
void doWork(const QString ¶meter) {
// ...
emit resultReady(result);
}
signals:
void resultReady(const QString &result);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString)));
connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
QThread的接口函数:
- QThread(QObject* parent=0);new一个QThread,父类有QThread的所有权,通过start()启动线程
- ~QThread ()销毁线程,在销毁线程前等待finished()信号
- exit ( int returnCode = 0 )告诉线程的事件循环通过返回码离开,returnCode=0表示success
- isFinished()const,isRunning()const返回是否结束和是否运行
- Priority priority () const返回线程运行的优先级,如果线程没运行返回7
- setPriority ( Priority priority )设置线程优先级
- setStackSize ( uint stackSize )设置最大线程堆栈,如果不设置由操作系统自动分配,且stackSize()返回0
- wait( unsigned long time = ULONG_MAX )如果是默认值则wait()将不会超时
- void quit ()告诉线程的事件循环退出并且返回0值
- void start ( Priority priority = InheritPriority )以某种优先级开启一个线程
- void terminate ()终止当前线程。线程或许不会立即被终止,依赖于线程的调度策略
bool isStopThread = false;
class Athread:public QThread{
public:
void stop(){isStopThread =true;}
void run()
{
while(!is_runnable) {
qDebug()<<"thread start:"<<QThread::currentThreadId();
}
}
};
Athread Athd;
Athd.start();
//安全退出
Athd.stop();
Athd.quit();
Athd.wait();
引用:https://www.cnblogs.com/ybqjymy/p/12210967.html
1.2 QSemaphore使用方法
QMutex实现了对共享变量安全访问的机制,但是一个Mutex只能锁住一个共享变量。QSemaphore解决了多线程对共享资源的竞争,资源能够获取多次,能够保护一定数量的同种资源。比如:使用Mutex, A线程锁住了S1,B线程只能等待A解锁才能访问S1;使用QSemaphore, A获取了S1资源,B想访问S资源,发现S1已经被A占有了且有剩余资源S2,可以访问S2
QSemaphore公有接口:
- Acquire(int n=1)尝试去n个资源。如果获取不到足够的资源,这个会一直锁住直到可以获取足够的资源(阻塞)
- Release(int n=1)释放n个资源,如果释放的大于初始化数量,则多余的为创建的资源
- avaliable()const 是get当前可获取的资源数量
- tryAcquire ( int n = 1 ) 尝试获取n个资源,如果available()<n,则返回false,否则返回true
- tryAcquire ( int n, int timeout )尝试获取n个资源,如果available()<n,若timeout ms后仍然无法获取则返回false,否则返回true
1.3 QWaitCondition使用方法
条件变量解决了两个线程同时访问某个共享区域/资源出现死锁问题;它是与Mutex锁一起使用的;wait方法其实包括两步,第一步解锁,第二步等待被唤醒;
QWaitCondition提供了2种基本操作包括4种方法:
- bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX )解锁并阻塞等待被唤醒
- bool wait ( QReadWriteLock * readWriteLock, unsigned long time = ULONG_MAX )
- void wakeAll ()使用同样的条件变量对象唤醒所有阻塞线程
- void wakeOne ()使用同样的条件变量对象唤醒一个被阻塞的线程
#include "qsemaphorestudycase.h"
#include <QSemaphore>
#include <QThread>
#include <QtGlobal>
#include <QTime>
#include <iostream>
#include <QKeyEvent>
#include <QMutex>
#include <QWaitCondition>
//-------------Macro---------------
#ifdef Q_WS_S60
const int DataSize = 300;
#else
const int DataSize = 100000;
#endif
const int BufferSize = 8192;
char buffer[BufferSize];
QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;
QMutex qmx;
QWaitCondition qwd;
//--------------class---------------------
class Producer:public QThread
{
public:
void
run()
{
qsrand(QTime(0,0,0).secsTo (QTime::currentTime ()));
for(int i=0;i<DataSize;++i)
{
qmx.lock ();
freeBytes.acquire ();
buffer[i%BufferSize] = "ACGT"[(int)qrand() % 4];
usedBytes.release (); //creat one
if(usedBytes.available ())
{
qwd.wakeOne ();
}
std::cout<<"consumer avaliable num are: "<<usedBytes.available ()<<std::endl;
qmx.unlock ();
}
}
};
class Consumer:public QThread
{
public:
void run()
{
for (int i=0;i<DataSize;++i)
{
qmx.lock ();
if( !usedBytes.tryAcquire ())
{
std::cout<<"Constumer cannot move..."<<std::endl;
qwd.wait(&qmx);
}
std::cout<<"Constumer's' moving..."<<std::endl;
#ifdef Q_WS_S60
QString text(buffer[i%BufferSize]);
freeBytes.release ();
emit stringConsumed(text);
#else
//fprintf(stderr,"%c",buffer[i%BufferSize]);
freeBytes.release ();//shi fang
qmx.unlock ();
#endif
}
fprintf(stderr,"\n");
std::cout<<"consumer stack size: "<<this->stackSize ()<<std::endl;
}
signals:
void stringConsumed(const QString& text);
protected:
bool finish;
};
Producer producer;
Consumer consumer;
QsemaphoreStudyCase::QsemaphoreStudyCase(QWidget *parent)
: QWidget(parent)
{
#ifdef USE_PRODUCT_CONSUMERMODE_
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
#else
producer.setStackSize (DataSize);
std::cout<<"consumer stack size: "<<producer.stackSize ()<<std::endl;
connect(&producer,SIGNAL(finished()),this,SLOT(stopthisthread()));
#endif
}
QsemaphoreStudyCase::~QsemaphoreStudyCase()
{
}
void QsemaphoreStudyCase::stopthisthread()
{
producer.quit ();
producer.wait ();
std::cout<<"producer has stoped! "<<std::endl;
}
void QsemaphoreStudyCase::keyPressEvent (QKeyEvent* keyevent)
{
if(keyevent->key () == Qt::Key_A)
{
consumer.start ();
//consumer.wait();
}
if(keyevent->key () == Qt::Key_B)
{
producer.start ();
//producer.wait ();
}
//keyPressEvent (keyevent);
}
1.4 QThreadpool使用方法
“进程的创建、撤销与切换存在着较大的时空开销”,因此出现了线程这种轻量级进程技术。如果还想进一步的降低系统资源开销,人们想出了一个办法,就是让执行完所有任务的线程不被销毁,让它处于“待命”的状态等待新的耗时操作“进驻”进来。Qt 提供了 QThreadPool 和 QRunnable 这两个类来对线程进行重复的使用。使用的方法一般是将耗时操作放入 QRunnable 类的 run() 函数中,然后整体把 QRunnable 对象扔到 QThreadPool 对象中就可以了。
QRunnalbe:能用到线程池的任务,基本上功能单一且并不需要和其他线程进行信号槽的通信
任务的统一封装形式:QRunnable
QRunnable()
virtual ~QRunnable()
bool autoDelete() const
void setAutoDelete(bool autoDelete)
virtual void run() = 0
只需要继承QRunnable并且重写run函数,扔给线程池即可
class HelloWorldTask : public QRunnable
{
void run() override
{
qDebug() << "Hello world";
}
};
HelloWorldTask *task = new HelloWorldTask();
QThreadPool::globalInstance->start(task);
QThreadpool只有线程QThread和任务QRunnable两种数据容器来管理任务调度,线程池的本质就是协调两种容器之间数据的交互。线程池中的线程也进行了二次封装,由继承于 QThread 的 QThreadPoolThread 类表示。该类在 QThread 的基础上扩展了两个私有变量:QRunnable * 和 QThreadPoolPrivate *,一个用于存储当前线程要执行的任务,一个用于存储线程池的指针。
因为每个 Qt 程序或者说每个进程都有一个全局 QThreadPool 对象,所以 globalInstance() 函数用于获取该对象的指针,那么用的时候直接用该静态函数即可,显式的创建一个局部线程池也是可以的。
#include <QCoreApplication>
#include <QThreadPool>
#include <QDebug>
#include <QRunnable>
class Task : public QRunnable
{
public:
void run()
{
qDebug() << "Hello";
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Task *task = new Task();
QThreadPool::globalInstance()->start(task);
return a.exec();
}
//显示创建
#include <QCoreApplication>
#include <QThreadPool>
#include <QDebug>
#include <QRunnable>
class Task : public QRunnable
{
public:
void run()
{
// 每个任务执行10秒,每一秒都输出当前线程
for (int i = 0; i < 10; ++i) {
qDebug() << QThread::currentThread();
QThread::sleep(1);
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThreadPool pool;
// 每隔1秒插入一个任务
for (int i = 0; i < 200; ++i) {
Task *task = new Task();
pool.start(task);
QThread::sleep(1);
}
return a.exec();
}
1.5 QTConcurrent使用方法
QThread 这种很底层的类,使用起来要考虑方方面面,用到底层的接口(如 QThread、信号量、互斥锁等),另外Qt 提供了高级函数来简化多线程编写,Qt Concurrent 。
- run 相关:执行函数用
- map 相关:处理容器中的每一项
- filter 相关:筛选容器中的每一项
//pro 文件添加“Qt += concurrent”并且在我们的 h 文件添加“#include <QtConcurrent>”
#include <QCoreApplication>
#include <QtConcurrent>
#include <QDebug>
void function()
{
qDebug() << "Hello world";
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QtConcurrent::run(function); //调用 run() 函数就可以在单独线程里运行
return a.exec();
}
//传参
extern void aFunction(int arg1, double arg2, const QString &string);
int a = 1;
double b = 2;
QString string = "hello";
QtConcurrent::run(aFunction, a, b, string);
//返回值
extern QString function();
QFuturen<QString> future = QtConcurrent::run(function);
QString result = future.result();
参考:
如何正确使用QThread https://www.cnblogs.com/liushui-sky/p/5829563.html