Linux Qt 开发之多线程

Linux Qt 开发之多线程

Qt 应用程序中所有的界面响应事件都在一个主线程中运行,当我们去调用QApplication对象的exec()方法时,Qt就不断去循环查询当前的事件队列中有没有事件发生,如果有则转去执行对应的槽函数,如果将过多的业务逻辑及耗时的操作放到当前的主线程中去执行,则界面的响应事件就无法及时得到响应,整个界面就会卡顿。

一般Qt应用程序的开发分为界面和后台两部分,在主线程中执行界面显示相关的操作,其他的业务逻辑则放到线程中去作为后台程序去执行,线程和线程及线程和主线程之间通过信号和槽进行通讯。

多线程开发中,常用的组件有线程、消息队列、信号量、互斥锁。一般一个线程维护一个消息队列,线程不断的查询或阻塞等待直到队列中有消息,则去执行相应的操作,并使用信号量进行线程间的同步,使用互斥锁保护线程间的共享资源。在Qt中也有线程、信号量、互斥锁组件,但消息队列则换成了信号&&槽机制,Qt中的每个线程自身单独维护一个事件队列,我们将不同的线程对象的信号和槽关联在一起,当事件发生时,对应信号则从一个线程发送到另一个线程的事件队列中,当前线程获取信号后,执行对应的槽函数,执行完成后再次进入事件监测循环中。

Qt实现多线程有两种方式,一种是基于QThread 类,使用时定义一个新的类继承QThread类,并重写QThread的run()方法,需要注意的是,这种方式只有run()方法是在新线程中执行的,其他的方法还是在创建对象的线程中执行。这种方法官方不推荐使用,已经被淘汰了,我们仅了解一下就可以。第二种方法是基于QObject类,使用时定义一个新的类继承QObject类,调用新类对象的moveToThread()方法绑定当前对象到一个线程中。该种方式新类的所有方法都被放在新的线程中执行。

1. QThread对象的创建

定义一个新类 继承 QObject类(新类的实例化不能设置任何父对象)。

实例化一个QThread对象。

实现新类中的槽函数。

QObject子类对象通过moveToThread将自己放到线程QThread对象中。

调用QThread对象的start函数启动线程。​​​​​​​

关联不同对象的信号和槽到新的类实例上。

2. QThread对象的销毁

 先把QObject在线程循环中释放(使用QObject::deleteLater函数),然后QThread::quit,然后QThread::wait。

1.   把线程的finished信号和object的deleteLater槽进行关联,线程退出时,释放QObject对象申请的内存空间。

2. 在析构函数中调用QThread::quit方法退出线程的事件循环。

3. 在析构函数中调用QThread::wait 回收线程资源。

我们看一下官方代码:

class Worker : public QObject
{
    Q_OBJECT
public slots:
    void doWork(const QString ¶meter) {
        QString result;
        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, &QThread::finished, worker, &QObject::deleteLater); //释放QObject资源
        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 &);
};

3. 线程的同步-----信号量QSemphore

互斥锁保护的资源同一时刻只能有一个线程能够获取使用权,有些资源是可以限定多个线程同时访问,那么这个时候可以使用信号量。在Qt中信号量为QSemaphore。

QSemaphore sem(2) //实例化信号量对象,并设定初始值为2.

sem.acquire();  //获取信号量,获取不到阻塞等待

sem.tryAcquire();  //获取信号量,获取不到立即返回

semaphore.release(); //释放信号量

4. 线程共享资源的保护----互斥锁QMutex、QMutexLocker

QMutex类提供了一个保护一段临界区代码的方法,他每次只允许一个线程访问这段临界区代码。

QMutex mutex; //实例化互斥对象

mutex.lock(); //对共享资源加锁,获取不到,阻塞等待

mutex.tryLock(int timeout = 0) //获取不到锁,超时返回,time==0,立即返回。

mutex.unlock(); //释放锁

Qt提供了QMutexLocker类何以简化互斥量的处理,它在构造函数中接受一个QMutex对象作为参数并将其锁定,在析构函数中解锁这个互斥量。

QMutex mutex;
void func()
{
    QMutexLocker locker(_mutex);       //func()执行完后,自动释放锁。
}

5. 读写锁QReadWriteLocker、QReadLocker、QWriteLocker

不常用,暂不作说明。

6. QWaitCondition允许一个线程在一定条件下唤醒其他线程,生产这消费这模型

 void Producer::run()  //生产者
   {  
       for (int i = 0; i < DataSize; ++i) {  
           mutex.lock();  
           if (usedSpace == BufferSize)  
               bufferIsNotFull.wait(&mutex);  
           buffer[i % BufferSize] = "MING"[uint(rand()) % 4];  
           ++usedSpace;  
           bufferIsNotEmpty.wakeAll();  
        mutex.unlock();  
        }  
   }

void Consumer::run()   //消费者
 {  
     forever {  
         mutex.lock();  
         if (usedSpace == 0)  
             bufferIsNotEmpty.wait(&mutex);  
         cerr << buffer[i % BufferSize];  
         --usedSpace;  
         bufferIsNotFull.wakeAll();  
      mutex.unlock();  
  }  
  cerr << endl;  
  }

 bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX ); 

这个函数做下说明,该函数将互斥量解锁并在此等待,它有两个参数,第一个参数为一个锁定的互斥量,第二个参数为等待时间。

调用wait()操作的线程使得作为参数的互斥量在调用前变为锁定状态,然后自身被阻塞变成为等待状态直到满足以下条件:

其他线程调用了wakeOne()或者wakeAll()函数,这种情况下将返回"true"值。

第二个参数time超时(以毫秒记时),该参数默认情况是ULONG_MAX,表示永不超时,这种情况下将返回"false"值。

wait()函数返回前会将互斥量参数重新设置为锁定状态,从而保证从锁定状态到等待状态的原则性转换。

7. QTimer定时器
头文件  #include

QTimer *timer = new QTimer(this);      //this 指定父对象,可自动回收

connect(timer, SIGNAL(timeout()), this, SLOT(update()));

timer->start(1000);

 
版权声明:如无特殊标注,文章均来自网络,本站编辑整理,转载时请以链接形式注明文章出处,请自行分辨。

本文链接:https://www.shbk5.com/dnsj/74998.html