线程云集(一)——QT线程

目录

1.1 QThread使用方法

1.2 QSemaphore使用方法

1.3 QWaitCondition使用方法

1.4 QThreadpool使用方法

1.5 QTConcurrent使用方法


前言:目前项目中使用到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的接口函数:

  1. QThread(QObject* parent=0);new一个QThread,父类有QThread的所有权,通过start()启动线程
  2. ~QThread ()销毁线程,在销毁线程前等待finished()信号
  3. exit ( int returnCode = 0 )告诉线程的事件循环通过返回码离开,returnCode=0表示success
  4. isFinished()const,isRunning()const返回是否结束和是否运行
  5. Priority    priority () const返回线程运行的优先级,如果线程没运行返回7
  6. setPriority ( Priority priority )设置线程优先级
  7. setStackSize ( uint stackSize )设置最大线程堆栈,如果不设置由操作系统自动分配,且stackSize()返回0
  8. wait( unsigned long time = ULONG_MAX )如果是默认值则wait()将不会超时
  9. void quit ()告诉线程的事件循环退出并且返回0值
  10. void start ( Priority priority = InheritPriority )以某种优先级开启一个线程
  11. void terminate ()终止当前线程。线程或许不会立即被终止,依赖于线程的调度策略
bool  isStopThread = false;

class Athread:public QThread{
     public:
     void stop(){isStopThread =true;}
     void run()
     {
        while(!is_runnable) {
            qDebug()<<"thread start:"<

线程云集(一)——QT线程_第1张图片

引用: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公有接口:

  1. Acquire(int n=1)尝试去n个资源。如果获取不到足够的资源,这个会一直锁住直到可以获取足够的资源(阻塞)
  2. Release(int n=1)释放n个资源,如果释放的大于初始化数量,则多余的为创建的资源
  3. avaliable()const 是get当前可获取的资源数量
  4. tryAcquire ( int n = 1 ) 尝试获取n个资源,如果available()
  5. tryAcquire ( int n, int timeout )尝试获取n个资源,如果available()

1.3 QWaitCondition使用方法

条件变量解决了两个线程同时访问某个共享区域/资源出现死锁问题;它是与Mutex锁一起使用的;wait方法其实包括两步,第一步解锁,第二步等待被唤醒;

QWaitCondition提供了2种基本操作包括4种方法:

  1. bool    wait ( QMutex * mutex, unsigned long time = ULONG_MAX )解锁并阻塞等待被唤醒
  2. bool    wait ( QReadWriteLock * readWriteLock, unsigned long time = ULONG_MAX )
  3. void    wakeAll ()使用同样的条件变量对象唤醒所有阻塞线程
  4. void    wakeOne ()使用同样的条件变量对象唤醒一个被阻塞的线程
#include "qsemaphorestudycase.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

//-------------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;istackSize ()<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:能用到线程池的任务,基本上功能单一且并不需要和其他线程进行信号槽的通信

引用:https://zhuanlan.zhihu.com/p/52612180

任务的统一封装形式: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 
#include 
#include 
#include 

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 
#include 
#include 
#include 

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 ”
#include 
#include 
#include 

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 future = QtConcurrent::run(function);
QString result = future.result();

 

参考:

如何正确使用QThread  https://www.cnblogs.com/liushui-sky/p/5829563.html

你可能感兴趣的:(多线程,QT)