赞
踩
QThread(线程),代表一个在应用程序中可以独立控制的线程,可以和进程中的其他线程分享数据。QThread 对象管理程序中的一个控制线程。QThreads 在 run() 中开始执行。默认情况下,run() 通过调用 exec() 来启动事件循环,并在线程内运行 Qt 事件循环
- 继承QThread 重写run函数
- 继承QObject 通过moveToThread(thread),交给thread执行
- 继承QRunnable 重写run函数,但必须使用线程池(QThreadPool)运行
继承QThread的特点:
- 可以通过信号槽与外界进行通信
- 线程中的对象必须在run函数中创建
- 每次新建一个线程都需要继承QThread,实现一个新类,使用不太方便。
- 要自己进行资源管理,线程释放和删除。并且频繁的创建和释放会带来比较大的内存开销。
(适用场景:那些常驻内存的任务)
继承QObject 通过moveToThread(thread)的特点:
- 使用比较简单方便
- 但只能执行槽函数中的内容,需要通过信号与槽的方式连接
- 创建对象时不能指定父类,如果指定父类,那么将无法调用到线程中
注意:线程中无法使用任何界面部件类
- class Thread : public QThread //继承QThread
- {
- Q_OBJECT
- public:
- explicit Thread(QObject *parent = nullptr);
- protected:
- void run() override;//重写run函数
- };
-
- void Thread::run()
- {
- //执行比较耗时的任务
- }
-
-
- //在主类中调用
-
- Thread thread1;
-
- thread.start();//开起线程,就会执行run函数
- class Worker : public QObject //继承自QObject
- {
- Q_OBJECT
-
- public slots:
- void doWork(const QString ¶meter) {
- QString result;
- /* ... here is the expensive or blocking operation ... */
- 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);//移动到线程种执行
- //当线程执行完成后,销毁work对象
- connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
- connect(this, &Controller::operate, worker, &Worker::doWork);
- connect(worker, &Worker::resultReady, this, &Controller::handleResults);
- workerThread.start();//开启线程
- }
- ~Controller() {
- workerThread.quit();//关闭线程
- workerThread.wait();//堵塞线程
- }
- public slots:
- void handleResults(const QString &);
- signals:
- void operate(const QString &);
QThread 常用的函数:
exec() | 进入事件循环并等待 exit() 被调用,返回传递给exit() 的值。如果通过 quit() 调用 exit(),则返回的值为 0。 此函数旨在从 run() 中调用。必须调用此函数才能启动事件处理 |
exit() | 告知线程的事件循环以返回代码退出 |
currentThread() | 返回指向管理当前正在执行的线程的 QThread 的指针。 |
idealThreadCount() | 返回可在系统上运行的理想线程数,如果无法检测到处理器内核数,则此函数返回 1。 |
isDone() | 如果线程完成返回true 否则返回 false |
isRunning() | 如果线程正在运行返回true 否则返回 false |
msleep() | 强制当前线程休眠毫秒 |
sleep() | 强制当前线程休眠几秒钟,此功能不保证准确性。在重负载条件下,应用程序可能会休眠超过秒。 |
usleep() | 强制当前线程休眠 usecs微秒,此功能不保证准确性。在重负载条件下,应用程序可能比 usecs 休眠时间更长 |
requestInterruption() | 请求中断线程。该请求是建议性的,由线程上运行的代码决定是否以及如何对此类请求进行操作。此函数不会停止线程上运行的任何事件循环,也不会以任何方式终止它。 |
run() | 线程的起点,调用 start() 后,新创建的线程调用此函数,您可以重新实现此函数以促进高级线程管理 |
quit() | 告知线程的事件循环退出并返回代码 0(成功)。等效于调用 QThread::exit(0) |
wait() | 阻塞线程,直到如果线程已完成,此函数将返回 true。如果线程尚未启动,它也返回 true。或到了最后期限。如果达到截止时间,此函数将返回 false |
start() | 通过调用 run() 开始执行线程。操作系统将根据优先级参数调度线程。如果线程已在运行,则此函数不执行任何操作 |
terminate() | 终止线程的执行。线程可能会也可能不会立即终止,具体取决于操作系统的调度策略。在 terminate() 之后使用 QThread::wait() 可以肯定。 |
finished() | 此信号在完成执行之前从关联的线程发出 |
started() | 在调用run() 函数之前,当关联线程开始执行时,此信号从关联线程发出 |
例子:
主界面有,开启,暂停,关闭按钮,在一个QLabel显示线程中的数据
创建一个myThread类继承自QThread
- class myThread : public QThread
- {
- Q_OBJECT
- public:
- explicit myThread(QObject *parent = nullptr);
-
- void stop_thread()//暂停
- {
- thread_run=false;//设置为不可执行
- }
-
- void run()//重写run函数 (函数直接在这里实现,比较好查看)
- {
- while(thread_run)//如果可以执行
- {
- a+=10;
- emit show_number(a);//触发信号
- sleep(1);//休眠5秒
- }
- thread_run=true;
-
- }
- protected:
- volatile bool thread_run=true;//判断是否可以运行
- int a=0;
-
- signals:
- void show_number(int i);//发送信号给主窗口,显示数据
-
- };
主类中的使用:
.cpp文件中:
- Widget::Widget(QWidget *parent)
- : QWidget(parent)
- , ui(new Ui::Widget)
- {
- ui->setupUi(this);
- myThread *thread=new myThread;
- qDebug()<<"本机线程数"<<thread->idealThreadCount();//查看自己的线程数
- connect(ui->pushButton,&QPushButton::clicked,[=]()//开启线程
- {
- thread->start();
- qDebug()<<"线程已开启";
- qDebug()<<"线程id"<<thread->currentThreadId();
- });
- connect(ui->pushButton_2,&QPushButton::clicked,[=]()//暂停线程
- {
- thread->stop_thread();//暂停
- qDebug()<<"线程已暂停";
- });
- connect(ui->pushButton_3,&QPushButton::clicked,[=]()//结束线程
- {
- ui->lcdNumber->display(0);
- thread->stop_thread();//暂停
- thread->quit();
- thread->wait();
- thread->deleteLater();//销毁
- ui->pushButton->setEnabled(false);//开启不可点击
- });
- connect(thread,SIGNAL(show_number(int)),this,SLOT(show_data(int)));
-
- }
- void Widget::show_data(int s)
- {
- ui->lcdNumber->display(s);
- }
-
- Widget::~Widget()
- {
- delete ui;
- }
-
在整个文档中,重入和线程安全用于标记类和函数,从而表明怎样在多线程应用中使用它们。
以上可以得出结论:线程安全函数始终是可重入的,但可重入函数并不总是线程安全的。
通过扩展,如果可以从多个线程安全地调用一个类的成员函数,只要每个线程使用该类的不同实例,则该类就被称为可重入类。如果可以从多个线程安全地调用该类的成员函数,则该类是线程安全的,即使所有线程都使用该类的同一实例也是如此。
c++类通常是可重入的,因为它们只能访问自己的成员数据。任何线程都可以在可重入类的实例上调用成员函数,只要没有其他线程可以同时调用该类的同一实例上的成员函数。
例如:
- class Counter
- {
- public:
- Counter() { n = 0; }
-
- void increment() { ++n; }
- void decrement() { --n; }
- int value() const { return n; }
-
- private:
- int n;
- };
上面的类是可重入的,但这并不是线程安全的,当有多个线程同时修改类的一个成员变量,可能会
会产生多种结果 。因为++和--的操作并不是总是原子的(原子操作是指一个操作不会被其他线程中断)。它们会分为3个机械指令:
当有多个线程同时加载变量的旧值,然后递增它们的寄存器,然后将其存储回去,最终它们会相互覆盖,变量只会递增一次。
显然,当有多个线程访问时,访问必须序列化,当有一个线程访问时,其他线程必须等待。
例如:使用QMutex保护数据的访问。
- class Counter
- {
- public:
- Counter() { n = 0; }
-
- void increment() { QMutexLocker locker(&mutex); ++n; }
- void decrement() { QMutexLocker locker(&mutex); --n; }
- int value() const { QMutexLocker locker(&mutex); return n; }
-
- private:
- mutable QMutex mutex;
- int n;
- };
在构造函数中自动锁定互斥锁,在函数结束时调用析构函数时解锁互斥锁,锁定互斥锁可确保序列化来自不同线程的访问。数据成员mutex是用限定符mutable声明的,因为我们需要锁定和解锁value中的互斥锁,因为这是一个 const 函数。
线程的目的是允许并行运行,但有时线程必须停止等待其他线程。例如,如果两个线程尝试访问同一个变量,这样的话结果是未定义的。强制线程相互等待的原则成为互斥,是一种保护共享资源的常用技术。
同步线程类:
- QMutex 互斥锁
- QReadWriteLock 读-写锁
- QSemaphore 信号量
- QWaitCondition 条件变量
QMutex(互斥锁)
提供一个互斥锁,在任何事件至多有一个线程可以获得mutex。如果一个线程尝试获得mutex,而mutex已经锁住,那么这个线程将会睡眠
QReadWriteLock (读-写锁)
读-写锁,于QMutex相似,但它对共享数据进行访问的区分,分为“读”,“写”访问,允许多个线程同时对数据进行“读”访问,QReadWiteLock的并行度大于QMutex
QSemaphore 信号量
QSemaphore是QMutex的概括,可保护一定数量的相同资源。相比之下,QMutex 只保护一种资源。信号量示例显示了信号量的典型应用:同步对生产者和使用者之间循环缓冲区的访问
QWaitCondition 条件变量
QWaitCondition,允许一个线程在一些条件满足时唤醒其他线程, 不是通过强制执行互斥而是通过提供条件变量来同步线程。一个或多个线程可以被堵塞来等待一个QWaitCondition,使用wakeOne()可以唤醒一个随机选取的等待的线程,使用wakeAll()可以唤醒所有正在等待的线程
QMutex通常和QMutexLocker一起使用,才可以确保这可以轻松确保锁定和解锁的执行一致。
常用函数:
isRecursive() | 判断互斥锁是否为递归(Qt 5.7引入) |
lock() | 锁定互斥锁,如果另一个线程锁定了互斥锁,则此调用将阻塞,直到该线程解锁它 |
tryLock(int ) | 尝试锁定互斥锁,如果已获得锁,则函数返回true 否则返回 false,可以设置等待时间。如果获得锁则必须使用unlock()解锁互斥锁,这样才能在另一个线程才能成功锁定它。 |
try_lock() | 尝试锁定互斥锁,如果已获得锁,则函数返回true 否则返回 false,提供此功能是为了与标准库概念兼容,等价于tryLock()(Qt 5.8 引入) |
try_lock_for() | 尝试锁定互斥锁,如果已获得锁,则函数返回true 否则返回 false, 如果获得锁,必须使用unlock()解锁互斥锁 如果为递归互斥锁,允许在同一线程的容易个互斥锁上多次调用该函数 如果此互斥锁是非递归互斥锁,则在尝试递归锁定互斥锁时,此函数将始终返回 false。(Qt 5.8 引入) |
try_lock_until() | 尝试锁定互斥锁,如果已获得锁,则函数返回true 否则返回 false, 如果获得锁,必须使用unlock()解锁互斥锁 如果为递归互斥锁,允许在同一线程的容易个互斥锁上多次调用该函数 如果此互斥锁是非递归互斥锁,则在尝试递归锁定互斥锁时,此函数将始终返回 false。(Qt 5.8 引入) |
unlock() | 解锁互斥锁。 |
QMutex::QMutex(QMutex::RecursionMode mode)
创建一个互斥锁时可以设置模式
QMutex::Recursive | 在这种模式下,线程可以多次锁定同一个互斥锁,并且在进行相应数量的 unlock() 调用之前,互斥锁不会被解锁 |
QMutex::NonRecursive | 在此模式下,线程只能锁定一次互斥锁 |
互斥锁使用场景:当一个变量同时被多个线程访问
- int number=10;
-
- void text()
- {
- number*=2;
- number+=5;
- }
- void text1()
- {
- number+=10;
- number*3;
- }
正常调用 text()和text1()的话
- text() number=2*10=20+5=25
- text1() number=25+10=30*3=90
同时调用的话:可能会发生以下情况
- 线程1调用text()
- number=10*2=20;
- 线程2调用text1(),现在text()的调用暂停
- number=20+10=30
- number=30*3=90;
- 继续完成线程text()
- number=90+5=95
为了防止以上情况,可以上个互斥锁,使得调用完某个函数才能调用其他函数。
- QMutex mutex;//互斥锁
-
- int number=10;
-
- void text()
- {
- mutex.lock();//上锁
- number*=2;
- number+=5;
- mutex.unlock();//解锁
- }
-
- void text1()
- {
- mutex.lock();//上锁
- number+=10;
- number*=3;
- mutex.unlock();//解锁
- }
QMutexLocker类是一个方便点的类,可以简化锁定和解锁的互斥锁。
使用方法:QMutexLocker应该在需要锁定QMutex的函数中创建,创建一个QMutexLock时,互斥锁被锁定。(更加方便)
函数:
mutex(() | 返回正在运行的互斥锁 |
relock() | 重新锁定未锁定的互斥锁锁。 |
unlock() | 解锁此互斥锁 |
使用QMutex的情况:需要在分支中解锁互斥锁
- int complexFunction(int flag)
- {
- mutex.lock();
-
- int retVal = 0;
-
- switch (flag) {
- case 0:
- case 1:
- retVal = moreComplexFunction(flag);
- break;
- case 2:
- {
- int status = anotherFunction();
- if (status < 0) {
- mutex.unlock();
- return -2;
- }
- retVal = status + flag;
- }
- break;
- default:
- if (flag > 10) {
- mutex.unlock();
- return -1;
- }
- break;
- }
-
- mutex.unlock();
- return retVal;
- }
QMutexLocker的使用
- int complexFunction(int flag)
- {
- QMutexLocker locker(&mutex);//创建一个QmutexLocker对象
-
- int retVal = 0;
-
- switch (flag) {
- case 0:
- case 1:
- return moreComplexFunction(flag);
- case 2:
- {
- int status = anotherFunction();
- if (status < 0)
- return -2;
- retVal = status + flag;
- }
- break;
- default:
- if (flag > 10)
- return -1;
- break;
- }
-
- return retVal;
- }
当函数执行完后,QMutexLocker对象销毁时,互斥锁会解锁,就不用每个分支去解锁。
使用QMutexLocker::mutex()可以获取当前正在运行的互斥锁。
读写锁是一种同步工具,用于保护可以访问以进行读取和写入的资源。如果要允许多个线程同时具有只读访问权限,则这种类型的锁定很有用,但是一旦一个线程想要写入资源,就必须阻止所有其他线程,直到写入完成。
函数:
lockForRead() | 锁定读取, 如果另一个线程已锁定写入,此函数将阻止当前线程。 如果线程已锁定写入,则无法锁定读取。 |
lockForWrite() | 锁定写入 如果另一个线程(包括当前线程)已锁定以进行读取或写入,则此函数将阻止当前线程 |
tryLockForRead() | 尝试锁定以进行读取,如果线程已锁定写入,则无法锁定读取 |
tryLockForWrite() | 尝试锁定以进行写入,如果线程已锁定以进行读取,则无法锁定写入 |
unlock() | 解锁 |
QReadWriteLock通常使用于经常读取数据,可以多个线程同时读取数据。
- QReadWriteLock lock;
-
- void ReaderThread::run()
- {
- ...
- lock.lockForRead();
- read_file();
- lock.unlock();
- ...
- }
-
- void WriterThread::run()
- {
- ...
- lock.lockForWrite();
- write_file();
- lock.unlock();
- ...
- }
信号量是互斥锁的概括。虽然互斥锁只能锁定一次,但可以多次获取信号量。信号量通常用于保护一定数量的相同资源
函数:
acquire(int n) | 尝试获取n个由信号量保护的资源,当资源不够时将堵塞直到资源足够。 |
release(int n) | 释放由信号量保护的 n 个资源, 此函数也可用于“创建”资源。 |
available() | 返回信号灯当前可用的资源数。这个数字永远不能是负数。 |
tryAcquire(int n) | 尝试获取由信号量保护的资源,成功返回true,否则返回false |
tryAcquire(int n,int timeout ) | 尝试获取由信号量保护的资源,成功返回true,否则返回false,调用最多等待timeout秒 |
QSemaphore的创建
QSemaphore::QSemaphore(int n=0)
创建新的信号量,并将其保护的资源数初始化为 n(默认为 0)。
- QSemaphore sem(5);
- sem.acquire(3);
- qDebug()<<"资源还有"<<sem.available()<<"个";
- sem.acquire(2);
- qDebug()<<"资源还有"<<sem.available()<<"个";
- sem.release(5);
- qDebug()<<"资源还有"<<sem.available()<<"个";
当释放的资源多余需要释放的资源时,多余的会进行创建
- QSemaphore sem(5);
- sem.acquire(3);
- qDebug()<<"资源还有"<<sem.available()<<"个";
- sem.release(5);
- qDebug()<<"资源还有"<<sem.available()<<"个";
- sem.release(10);
- qDebug()<<"资源还有"<<sem.available()<<"个";
当资源少于需要获取的资源时,不会获取成功。
- QSemaphore sem(5);
- if(sem.tryAcquire(7)){
- qDebug()<<"获取成功";
- }
- else
- {
- qDebug()<<"获取失败";
- }
QSemaphore信号量的生产者消费者问题。
全局变量 :
- const int DataSize=1000;//生产者的数据量
- const int BufferSize=800;//缓冲区大小
- char buffer[Buffersize];
- QSemaphore freeBytes(BufferSize);//控制缓冲区的信号量
- QSemaphore usedBytes;//控制已经使用的缓冲区
生产者类:
- class Producer :public QThread
- {
- public:
- void run();
- }
-
- void Producer::run()
- {
- qsrand(QTime(0.0.0).secsTo(QTime::currentTime()));//随机数
- for(int i=0;i<DataSize;++i){
- freeBytes.acquire();
- buffer[%BufferSize]="ACGT"[(int)qrand %4];
- qDebug()<<QString("Producer:%1").arg(buffer[i%buffersize]);
- usedBytes.release();
-
- }
消费者类:
- class Consumer :public QThread
- {
- public:
- void run();
- }
-
- void Producer::run()
- {
- for(int i=0;i<DataSize;++i){
- userBytes.acquire();
- qDebug()<<QString("Producer:%1").arg(buffer[i%buffersize]);
- freeBytes.release();
- }
-
- }
main()
- int main(int argc, char *argv[])
- {
- QCoreApplication app(argc, argv);
- Producer producer;//生产者
- Consumer consumer;//消费者
- producer.start();//开启线程
- consumer.start();//开启线程
- //两个线程调用wait(),阻塞线程,确保两个线程在退出前都有时间能完成main()
- producer.wait();
- consumer.wait();
- return 0;
- }
允许一个线程在一些条件满足时唤醒其他线程, 不是通过强制执行互斥而是通过提供条件变量来同步线程。一个或多个线程可以被堵塞来等待一个QWaitCondition,使用wakeOne()可以唤醒一个随机选取的等待的线程,使用wakeAll()可以唤醒所有正在等待的线程
函数:
wait(QMutex*,time) | 释放锁定的互斥锁并等待等待条件 |
wakeOne() | 唤醒一个等待等待条件的线程。唤醒的线程取决于操作系统的调度策略,无法控制或预测。如果要唤醒特定线程,解决方案通常是使用不同的等待条件,并让不同的线程等待不同的条件 |
wakeAll() | 唤醒等待等待条件的所有线程。线程的唤醒顺序取决于操作系统的调度策略,无法控制或预测。 |
notify_one() | 提供此函数是为了与 STL 兼容。它等效于 wakeOne()Qt 5.8中引入 |
notify_all() | 提供此函数是为了与 STL 兼容。它等效于 wakeAll()Qt 5.8中引入 |
QWaitCondition的示例:
使用QWaitCondition的QMutex解决生产者-消费者问题
设置全局变量:
- const int DataSize = 100000;//生产者将生成的数据量
-
- const int BufferSize = 8192;//缓冲区
- char buffer[BufferSize];
-
- //两个等待条件,一个互斥锁和计数器
- QWaitCondition bufferNotEmpty;
- QWaitCondition bufferNotFull;
- QMutex mutex;
- int numUsedBytes = 0;
生产者类:
- class Producer : public QThread
- {
- public:
- Producer(QObject *parent = NULL) : QThread(parent)
- {
- }
-
- void run() override
- {
- for (int i = 0; i < DataSize; ++i) {
- mutex.lock();//上锁
- if (numUsedBytes == BufferSize)//检查缓冲区是否已满
- bufferNotFull.wait(&mutex);//已满的话等待条件满足
- mutex.unlock();//解锁
- //存放数据(随机数)
- buffer[i % BufferSize] = "ACGT"[QRandomGenerator::global()->bounded(4)];
-
- mutex.lock();//上锁
- ++numUsedBytes;计数器+1
- bufferNotEmpty.wakeAll();//唤醒全部线程
- mutex.unlock();//解锁
- }
- }
- };
消费者类:
- class Consumer : public QThread
- {
- Q_OBJECT
- public:
- Consumer(QObject *parent = NULL) : QThread(parent)
- {
- }
-
- void run() override
- {
- for (int i = 0; i < DataSize; ++i) {
- mutex.lock();
- if (numUsedBytes == 0)//检查缓冲区是否为空
- bufferNotEmpty.wait(&mutex);
- mutex.unlock();
-
- fprintf(stderr, "%c", buffer[i % BufferSize]);//输出内容
-
- mutex.lock();
- --numUsedBytes;//计数器-1
- bufferNotFull.wakeAll();
- mutex.unlock();
- }
- fprintf(stderr, "\n");
- }
-
- signals:
- void stringConsumed(const QString &text);
- };
main函数:
- int main(int argc, char *argv[])
- {
- QCoreApplication app(argc, argv);
- Producer producer;//生产者
- Consumer consumer;//消费者
- producer.start();//开启线程
- consumer.start();//开启线程
- //两个线程调用wait(),阻塞线程,确保两个线程在退出前都有时间能完成main()
- producer.wait();
- consumer.wait();
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。