Qt线程(两种QThread类的详细使用方式)

更新时间:2023-06-30 09:58:32 阅读: 评论:0

Qt线程(两种QThread类的详细使⽤⽅式)
Qt提供QThread类以进⾏多任务处理。与多任务处理⼀样,Qt提供的线程可以做到单个线程做不到的事情。例如,⽹络应⽤程序中,可以使⽤线程处理多种连接器。
QThread继承⾃QObject类,且提供QMutex类以实现同步。线程和进程共享全局变量,可以使⽤互斥体对改变后的全局变量值实现同步。因此,必须编辑全局数据时,使⽤互斥体实现同步,其它进程则不能改变或浏览全局变量值。
什么是互斥体?
互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex))。互斥体禁⽌多个线程同时进⼊受保护的代码“临界区”(critical ction)。
在任意时刻,只有⼀个线程被允许进⼊代码保护区。任何线程在进⼊临界区之前,必须获取(acquire)与此区域相关联的互斥体的所有权。如果已有另⼀线程拥有了临界区的互斥体,其他线程就不能再进⼊其中。这些线程必须等待,直到当前的属主线程释放(relea)该互斥体。
什么时候需要使⽤互斥体呢?
互斥体⽤于保护共享的易变代码,也就是,全局或静态数据。这样的数据必须通过互斥体进⾏保护,以防⽌它们在多个线程同时访问时损坏。
1class MyThread : public QThread
2 {
3    Q_OBJECT
4protected:
5void run();
6 };
7
8void MyThread :: run(){
9    ...
10 }
如上述代码所⽰,如果要创建线程,则必须继承QThread类。MyThread使⽤成员函数run()才会实现线程。
Qt提供的线程类
线程类说明
QAtomicInt 提供了Integer上与平台⽆关的Qtomic运算
QAtomicPointer 提供了指针上Atomic运算的模板函数
QFuture 显⽰异步运算结果的类
QFutureSynchronizer QFuture类简化同步⽽提供的类
QFutureWatcher 使⽤信号和槽,允许QFuture监听
QMutex 访问类之间的同步
QMutecLocker 简化Lock和Unlock Mutex的类
QReadWriteLock 控制读写操作的类
QReadLocker 为了读访问⽽提供的
QWriteLocker 为了写访问⽽提供的
QRunnable 正在运⾏的所有对象的⽗类,且定义了虚函数run()
QSemaphore ⼀般的Count互斥体类
QThread 提供与平台⽆关的线程功能的类
QThreadPool 管理线程的类2011专四答案
QThreadStorage 提供每个线程存储区域的类
QWaitCondition 确认线程间同步的类的状态值
同步QThread的类
为了同步线程,Qt提供了QMutex、QReadWriteLock、QSemaphore和QWaitCondition类。主线程等待与其他线程的中断时,必须进⾏同步。例如:两个线程同时访问共享变量,那么可能得不到预想的结果。因此,两个线程访问共享变量时,必须进⾏同步。
⼀个线程访问指定的共享变量时,为了禁⽌其他线程访问,QMutex提供了类似锁定装置的功能。互斥体激活状态下,线程不能同时访问共享变量,必须在先访问的线程完成访问后,其他线程才可以继续访问。
⼀个线程访问互斥体锁定的共享变量期间,如果其他线程也访问此共享变量,那么该线程将会⼀直处于休眠状态,直到正在访问的线程结束访问。这称为线程安全。
QReadWriteLock和QMutex的功能相同,区别在于,QReadWriteLock对数据的访问分为读访问和写访问。很多线程频繁访问共享变量时,与QMetex相对,使⽤QReadWriteLock更合适。
QSemaphore拥有和QMutex⼀样的同步功能,可以管理多个按数字识别的资源。QMutex只能管理⼀个资源,但如果使⽤QSemaphore,则可以管理多个按号码识别的资源。
条件符合时,QWaitCondition允许唤醒线程。例如,多个线程中某个线程被阻塞时,通过QWaitCondition提供的函数wakeOne()和wakeAll()可以唤醒该线程。
可重⼊性与线程安全
sharni vinson可重⼊性:两个以上线程并⾏访问时,即使不按照调⽤顺序重叠运⾏代码,也必须保证结果;
线程安全:线程并⾏运⾏的情况下,虽然保证可以使程序正常运⾏,但访问静态空间或共享(堆等内存对象)对象时,要使⽤互斥体等机制保证结果。
⼀个线程安全的函数不⼀定是可重⼊的;⼀个可重⼊的函数缺也不⼀定是线程安全的!
可重⼊函数主要⽤于多任务环境中,⼀个可重⼊的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执⾏的任何时刻中断它,转⼊OS调度下去执⾏另外⼀段代码,⽽返回控制时不会出现什么错误;⽽不可重⼊的函数由于使⽤了⼀些系统资源,⽐如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运⾏在多任务环境下的。
编写可重⼊函数时,若使⽤全局变量,则应通过关中断、信号量(即P、V操作)等⼿段对其加以保护。若对所使⽤的全局变量不加以保护,则此函数就不具有可重⼊性,即当多个线程调⽤此函数时,很有可能使有关全局变量变为不可知状态。
满⾜下列条件的函数多数是不可重⼊的:
函数体内使⽤了静态的数据结构和全局变量,若必须访问全局变量,利⽤互斥信号量来保护全局变量;;
函数体内调⽤了malloc()或者free()函数;
函数体内调⽤了标准I/O函数。
常见的不可重⼊函数有:
printf --------引⽤全局变量stdout
malloc --------全局内存分配表
free --------全局内存分配表
也就是说:本质上,可重⼊性与C++类或者没有全局静态变量的函数相似,由于只能访问⾃⾝所有的数据变量区域,所以即使有两个以上线程访问,也可以保证安全性。
QThread和QObjects
QThread类继承⾃QObjects类。因此,线程开始或结束时,QThread类发⽣发送信号事件。信号与槽的功能是QThread类从QObject类继承的,可以通过信号与槽处理开始或结束等操作,所以可以实现多线程。QObject是基于QTimer、QTcpSocket、QUdpSocket和QProcess之类的⾮图形⽤户界⾯的⼦类。
基于⾮图形⽤户界⾯的⼦类可以⽆线程操作。单⼀类运⾏某功能时,可以不需要线程。但是,运⾏单⼀类的⽬标程序的上级功能时,则必须通过线程实现。
线程A和线程B没有结束的情况下,应设计使主线程时间循环不结束;⽽若线程A迟迟不结束⽽导致主线程循环也迟迟不能结束,故也要防⽌线程A没有在⼀定时间内结束。
处理QThread的信号和槽的类型
Qt提供了可以决定信号与槽类型的枚举类,以在线程环境中适当处理事物。
决定信号与槽类型的枚举类
常量值说明
Qt::AutoConnection 0 如果其他线程中发⽣信号,则会插⼊队列,像QueuedConnection⼀样,否则如DirectConnection⼀样,直接连接到槽。发送信号时决定Connection类型。
Qt::DirectConnection 1 发⽣信号事件后,槽⽴即响应
Qt::QueuedConnection 2 返回收到的线程事件循环时,发⽣槽事件。槽在收到的线程中运⾏
Qt::BlockingQueuedConnection 3 与QueuedConnection⼀样,返回槽时,线程被阻塞。建⽴在事件发⽣处使⽤该类型
使⽤QtConcurrent类的并⾏编程
QtConcurrent类提供多线程功能,不使⽤互斥体、读写锁、等待条件和信号量等低级线程。使⽤QtConcurrent创建的程序会根据进程数⾃⾏调整使⽤的线程数。
QThread类
简述
QThread类提供了与系统⽆关的线程。
QThread代表在程序中⼀个单独的线程控制。线程在run()中开始执⾏,默认情况下,run()通过调⽤exec()启动事件循环并在线程⾥运⾏⼀个Qt的事件循环。
详细描述
QThread类可以不受平台影响⽽实现线程。QThread提供在程序中可以控制和管理线程的多种成员函数和信号/槽。通过QThread类的成员函数start()启动线程。
QThread通过信号函数started()和finished()通知开始和结束,并查看线程状态;可以使⽤isFinished()和isRunning()来查询线程的状态;使⽤函数exit()和quit()可以结束线程。
如果使⽤多线程,有时需要等到所有线程终⽌。此时,使⽤函数wait()即可。线程中,使⽤成员函数sleep()、msleep()和usleep()可以暂停秒、毫秒及微秒单位的线程。
⼀般情况下,wait()和sleep()函数应该不需要,因为Qt是⼀个事件驱动型框架。考虑监听finished()信号来取代wait(),使⽤QTimer来取代sleep()。
静态函数currentThreadId()和currentThread()返回标识当前正在执⾏的线程。前者返回该线程平台特定的ID,后者返回⼀个线程指针。
要设置线程的名称,可以在启动线程之前调⽤tObjectName()。如果不调⽤tObjectName(),线程的名称将是线程对象的运⾏时类型(QThread⼦类的类名)。
线程管理
可以将常⽤的接⼝按照功能进⾏以下分类:
线程启动
void start(Priority priority = InheritPriority) [slot]
调⽤后会执⾏run()函数,但在run()函数执⾏前会发射信号started(),操作系统将根据优先级参数调度线程。如果线程已经在运⾏,那么这个函数什么也不做。优先级参数的效果取决于操作系统的调度策略。特别是那些不⽀持线程优先级的系统优先级将会被忽略。
线程执⾏
大家好日语int exec() [protected]
进⼊事件循环并等待直到调⽤exit(),返回值是通过调⽤exit()来获得,如果调⽤成功则范围0。
void run() [virtual protected]
线程的起点,在调⽤start()之后,新创建的线程就会调⽤这个函数,默认实现调⽤exec(),⼤多数需要重新实现这个函数,便于管理⾃⼰的线程。该⽅法返回时,该线程的执⾏将结束。
线程退出
void quit() [slot]
告诉线程事件循环退出,返回0表⽰成功,相当于调⽤了QThread::exit(0)。
void exit(int returnCode = 0)
告诉线程事件循环退出。调⽤这个函数后,线程离开事件循环后返回,QEventLoop::exec()返回returnCode,按照惯例,0表⽰成功;任何⾮0值表⽰失败。
void terminate() [slot]
终⽌线程,线程可能会⽴即被终⽌也可能不会,这取决于操作系统的调度策略,使⽤terminate()之后再使⽤QThread::wait(),以确保万⽆⼀失。当线程被终⽌后,所有等待中的线程将会被唤醒。
警告:此函数⽐较危险,不⿎励使⽤。线程可以在代码执⾏的任何点被终⽌。线程可能在更新数据时被终⽌,从⽽没有机会来清理⾃⼰,解锁等等。。。总之,只有在绝对必要时使⽤此函数。
void requestInterruption()
请求线程的中断。该请求是咨询意见并且取决于线程上运⾏的代码,来决定是否及如何执⾏这样的请求。此函数不停⽌线程上运⾏的任何事件循环,并且在任何情况下都不会终⽌它。
线程等待
1void msleep(unsigned long mcs) [static]    //强制当前线程睡眠mcs毫秒
2
3void sleep(unsigned long cs) [static]    //强制当前线程睡眠cs秒
4
5void usleep(unsigned long ucs) [static]    //强制当前线程睡眠ucs微秒
6
7bool wait(unsigned long time = ULONG_MAX)    //线程将会被阻塞,等待time毫秒。和sleep不同的是,如果线程退出,wait会返回。
线程状态
ca logic1bool isFinished() const//线程是否结束
2nolo
3bool isRunning() const//线程是否正在运⾏
4
5bool isInterruptionRequested() const//如果线程上的任务运⾏应该停⽌,返回true。可以使⽤requestInterruption()请求中断。
6
7//此函数可⽤于使长时间运⾏的任务⼲净地中断。从不检查或作⽤于该函数返回值是安全的,但是建议在长时间运⾏的函数中经常这样做。注意:不要过于频繁调⽤,以保持较低的线程优先级
void tPriority(Priority priority)
设置正在运⾏线程的优先级。如果线程没有运⾏,此函数不执⾏任何操作并⽴即返回。使⽤的start()来启动⼀个线程具有特定的优先级。优先级参数可以是
QThread::Priority枚举除InheritPriortyd的任何值。
Qt多线程优先级
常量值优先级
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的使⽤⽅法有如下两种:
QObject::moveToThread()
blue point继承QThread类
QObject::moveToThread
⽅法描述:
necessary的用法定义⼀个继承于QObject的worker类,在worker类中定义⼀个槽slot函数doWork(),这个函数中定义线程需要做的⼯作;
在要使⽤线程的controller类中,新建⼀个QThread的对象和woker类对象,使⽤moveToThread()⽅法将worker对象的事件循环全部交由QThread对象处理;
建⽴相关的信号函数和槽函数进⾏连接,然后发出信号触发QThread的槽函数,使其执⾏⼯作。
例⼦:
1 #ifndef WORKER_H
2#define WORKER_H
3 #include <QObject>
4 #include<QDebug>
5 #include<QThread>
6class Worker:public QObject                    //work定义了线程要执⾏的⼯作
7 {
8    Q_OBJECT
9public:
10    Worker(QObject* parent = nullptr){}
11public slots:
12void doWork(int parameter)                        //doWork定义了线程要执⾏的操作
13    {
14        qDebug()<<"receive the execute signal---------------------------------";
15        qDebug()<<"    current thread ID:"<<QThread::currentThreadId();
16for(int i = 0;i!=1000000;++i)
17        {
18        ++parameter;
19        }
20        qDebug()<<"      finish the work and nt the resultReady signal\n";
21        emit resultReady(parameter);          //emit啥事也不⼲,是给程序员看的,表⽰发出信号发出信号
22    }
23
24 signals:
25void resultReady(const int result);              //线程完成⼯作时发送的信号
26 };
27
28#endif// WORKER_H
1 #ifndef CONTROLLER_H
2#define CONTROLLER_H
3 #include <QObject>
4 #include<QThread>
5 #include<QDebug>
6class Controller : public QObject            //controller⽤于启动线程和处理线程执⾏结果
7 {
8    Q_OBJECT
9    QThread workerThread;
10public:
好听的英文名字
11    Controller(QObject *parent= nullptr);
12    ~Controller();
13public slots:
14void handleResults(const int rslt)                        //处理线程执⾏的结果
15    {
16        qDebug()<<"receive the resultReady signal---------------------------------";
17        qDebug()<<"    current thread ID:"<<QThread::currentThreadId()<<'\n';
18        qDebug()<<"    the last result is:"<<rslt;
19    }
20 signals:
21void operate(const int);                        //发送信号触发线程
22 };
23
24#endif// CONTROLLER_H
1 #include "controller.h"
2 #include <worker.h>
3 Controller::Controller(QObject *parent) : QObject(parent)
4 {
5    Worker *worker = new Worker;
6    worker->moveToThread(&workerThread);            //调⽤moveToThread将该任务交给workThread
7
8    connect(this, SIGNAL(operate(const int)), worker, SLOT(doWork(int)));            //operate信号发射后启动线程⼯作
9    connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);            //该线程结束时销毁
10    connect(worker, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));            //线程结束后发送信号,对结果进⾏处理11
12    workerThread.start();                //启动线程
13    qDebug()<<"emit the signal to execute!---------------------------------";
14    qDebug()<<"    current thread ID:"<<QThread::currentThreadId()<<'\n';
15    emit operate(0);
16 }
17
18 Controller::~Controller()        //析构函数中调⽤quit()函数结束线程
19 {
20    workerThread.quit();
21    workerThread.wait();
22 }
继承QThread类
⽅法描述
⾃定义⼀个继承QThread的类MyThread,重载MyThread中的run()函数,在run()函数中写⼊需要执⾏的⼯作;
调⽤start()函数来启动线程。
例⼦:
1 #ifndef MYTHREAD_H
2#define MYTHREAD_H
3 #include<QThread>
4 #include<QDebug>
5class MyThread : public QThread
6 {
7    Q_OBJECT
8public:
9    MyThread(QObject* parent = nullptr);
10 signals:                //⾃定义发送的信号
11void myThreadSignal(const int);
12public slots:                //⾃定义槽
13void myThreadSlot(const int);
14protected:
15void run() override;
16 };
17
18#endif// MYTHREAD_H
1 #include "mythread.h"
2
3 MyThread::MyThread(QObject *parent)
4 {
5
6 }
7
8void MyThread::run()
9 {
10    qDebug()<<"myThread run() start to execute";
11    qDebug()<<"    current thread ID:"<<QThread::currentThreadId()<<'\n';
12int count = 0;
13for(int i = 0;i!=1000000;++i)
14    {
15      ++count;
16    }
17    emit myThreadSignal(count);
18    exec();
19 }
20
21void MyThread::myThreadSlot(const int val)
22 {
23    qDebug()<<"myThreadSlot() start to execute";
24    qDebug()<<"    current thread ID:"<<QThread::currentThreadId()<<'\n';
25int count = 888;
26for(int i = 0;i!=1000000;++i)
practidoing
27    {
28      ++count;
29    }
30 }
1 #include "controller.h"
2 #include <mythread.h>
3 Controller::Controller(QObject *parent) : QObject(parent)
4 {
5    myThrd = new MyThread;
6    connect(myThrd,&MyThread::myThreadSignal,this,&Controller::handleResults);
7    connect(myThrd, &QThread::finished, this, &QObject::deleteLater);            //该线程结束时销毁
8    connect(this,&Controller::operate,myThrd,&MyThread::myThreadSlot);
9
10    myThrd->start();
11    QThread::sleep(5);
12    emit operate(999);
13 }
14
15 Controller::~Controller()
16 {
17    myThrd->quit();
18    myThrd->wait();
19 }
两种⽅法的⽐较
两种⽅法来执⾏线程都可以,随便你的喜欢。不过看起来第⼆种更加简单,容易让⼈理解。不过我们的兴趣在于这两种使⽤⽅法到底有什么区别?其最⼤的区别在于:
moveToThread⽅法,是把我们需要的⼯作全部封装在⼀个类中,将每个任务定义为⼀个的槽函数,再建⽴触发这些槽的信号,然后把信号和槽连接起来,最后将这个类调⽤moveToThread⽅法交给⼀个QThread对象,再调⽤QThread的start()函数使其全权处理事件循环。于是,任何时候我们需要让线程执⾏某个任务,只需要发出对应的信号就可以。其优点是我们可以在⼀个worker类中定义很多个需要做的⼯作,然后发出触发的信号线程就可以执⾏。相⽐于⼦类化的QThread只能执⾏run()函数中的任务,moveToThread的⽅法中⼀个线程可以做很多不同的⼯作(只要发出任务的对应的信号即可)。
⼦类化QThread的⽅法,就是重写了QThread中的run()函数,在run()函数中定义了需要的⼯作。这样的结果是,我们⾃定义的⼦线程调⽤start()函数后,便开始执⾏run()函数。如果在⾃定义的线程类中定义相关槽函数,那么这些槽函数不会由⼦类化的QThread⾃⾝事件循环所执⾏,⽽是由该⼦线程的拥有者所在线程(⼀般都是主线程)来执⾏。如果你不明⽩的话,请看,第⼆个例⼦中,⼦类化的线
程的槽函数中输出当前线程的ID,⽽这个ID居然是主线程的ID!!事实的确是如此,⼦类化的QThread只能执⾏run()函数中的任务直到run()函数退出,⽽它的槽函数根本不会被⾃⼰的线程执⾏。
QThread的信号与槽
启动或终⽌线程时,QThread提供了信号与槽。
QThread的信号
北京英孚英语怎么样信号含义
void finished() 终⽌线程实例运⾏,发送信号
void started() 启动线程实例,发送信号
void terminated() 结束线程实例,则发送信号
QThread的槽
槽含义
void quit() 线程终⽌运⾏槽
void start(Priority) 线程启动槽
void terminate() 线程结束槽

本文发布于:2023-06-30 09:58:32,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/78/1069997.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:线程   函数   信号   访问   结束   互斥   循环   事件
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图