Qt深入浅出(十三)多线程与定时器

多线程

​ 我们之前的程序都是单线程运行,接下来我们开始引入多线程。就相当于以前的一个人在工作,现在多个人一起工作。

​ Qt中非常有必要使用多线程,这是因为,Qt应用是事件驱动型的,一旦某个事件处理函数处理时间过久,就会造成其它的事件得不到及时处理。

​ Qt中使用QThread来管理线程,一个QThread对象,就是一个线程。QThread对象也有消息循序exec()函数,用来处理自己这个线程的事件。

​ 实现多线程有两种方式。

1 第一种创建线程方式

  1. 首先要继承QThread

  2. 重写虚函数QThread::run


   
   
  1. [virtual protected] void QThread::run()
  2. /*
  3. * 基类QThread的run函数只是简单启动exec()消息循环
  4. */

例如:


   
   
  1. #include <QApplication>
  2. #include <QThread>
  3. #include <QDebug>
  4. class MyThread : public QThread
  5. {
  6. public:
  7.    void run()
  8.   {
  9.        qDebug() << "QThread begin" << endl;
  10.        qDebug() << "child thread" << QThread::currentThreadId() << endl;
  11.        QThread::sleep(5);
  12.        qDebug() << "QThread end"   << endl;
  13. exec();
  14.   }
  15. };
  16. int main(int argc, char** argv)
  17. {
  18.    QApplication app(argc, argv);
  19.    MyThread thread;
  20.    thread.start();
  21.    qDebug() << "main thread" << QThread::currentThreadId() << endl;
  22. QThread::sleep(5);
  23.   qDebug() << "main thread" << QThread::currentThreadId() << endl;
  24. thread.quit();
  25. qDebug() << "main thread thread.quit()" << endl;
  26. tread.wait();
  27. qDebug() << "main thread thread.wait()" << endl;
  28.    return app.exec();
  29. }

​ 使用QThread的quit可以退出线程的消息循环,有时候不是马上退出,需要等到cpu的控制权交还给线程的exec()。

        一般在子线程退出的时候需要主线程去回收资源,可以调用QThread的wait,等待子线程的退出,然后回收资源.

 

2 第二种创建线程方式

  1. 继承 QObject

  2. 实例化一个QThread对象

  3. 实现槽函数.

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

  5. 调用QThread对象的start函数启动线程

  6. 必须通过发射信号来让槽函数在线程中执行,发射的信号存放在线程exec()消息队列中。

    例如: 

    mywork.h


   
   
  1. #ifndef MYWORK_H
  2. #define MYWORK_H
  3. #include <QThread>
  4. #include <QDebug>
  5. class MyWork : public QObject
  6. {
  7.    Q_OBJECT
  8. public slots:
  9.    void workSlot()
  10.   {
  11.        qDebug() << "QThread begin" << endl;
  12.        qDebug() << "child thread" << QThread::currentThreadId() << endl;
  13.        QThread::sleep(5);
  14.        qDebug() << "QThread end"   << endl;
  15.   }
  16. };
  17. #endif // MYWORK_H

​ widget.cpp


   
   
  1. #include <QApplication>
  2. #include <QThread>
  3. #include <QDebug>
  4. #include "mywork.h"
  5. int main(int argc, char** argv)
  6. {
  7.    qDebug() << "main thread" << QThread::currentThreadId() << endl;
  8.    QApplication app(argc, argv);
  9.    QThread thread;
  10.    MyWork work;
  11.    work.moveToThread(&thread);
  12.    QObject::connect(&thread, SIGNAL(started()), &work,  SLOT(workSlot()));
  13.    thread.start();
  14.  
  15.    QThread::sleep(6);
  16.    qDebug() << "thread is runing" << thread.isRunning() << endl;
  17.    thread.quit(); //调用quit让线程退出消息循环,否则线程一直在exec循环中
  18.    thread.wait(); //调用完quit后紧接着要调用wait来回收线程资源
  19.    qDebug() << "thread is runing" << thread.isRunning() << endl;
  20.    return app.exec();
  21. }

需要特别注意的是:

  1. 线槽函数已经执行完进入线程exec()中,可以通过发射信号重新让槽函数在线程中执行。也可以通过quit()退出线程exec()。

  2. QObject派生类对象,将要调用moveToThread,不能指定一个主线程父对象托管内存。

  3. QWidget的对象及派生类对象都只能在GUI主线程运行,不能使用moveToThread移到子线程中,即使没有指定父对象。

  • 多线程对象内存释放

    既然QObject对象无法托管内存对象,那么到底是先释放线程对象,还是先释放这个QObject对象?

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

例如:

​ mywork.h


   
   
  1. #ifndef MYWORK_H
  2. #define MYWORK_H
  3. #include <QThread>
  4. #include <QDebug>
  5. class MyWork : public QObject
  6. {
  7.    Q_OBJECT
  8. public:
  9.    ~MyWork() { qDebug() << __FUNCTION__   << endl; }
  10. public slots:
  11.    void workSlot()
  12.   {
  13.        while(1)
  14.       {
  15.            qDebug() << "Work begin" << endl;
  16.            QThread::sleep(5);
  17.            qDebug() << "work end"   << endl;
  18.       }
  19.   }
  20.    void otherWorkSlot()
  21.   {
  22.        qDebug() << "otherWork begin" << endl;
  23.        QThread::sleep(5);
  24.        qDebug() << "otherWork end"   << endl;
  25.   }
  26. };
  27. #endif // MYWORK_H

​ widget.h


   
   
  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3. #include <QWidget>
  4. #include "mywork.h"
  5. class Widget : public QWidget
  6. {
  7.    Q_OBJECT
  8. public:
  9.    Widget(QWidget *parent = 0);
  10.    ~Widget();
  11. private:
  12.    QThread *_thread;
  13.    MyWork * _myWork;
  14. };
  15. #endif // WIDGET_H

​ widget.cpp


   
   
  1. #include "widget.h"
  2. #include <QPushButton>
  3. #include "mywork.h"
  4. #include <QThread>
  5. #include <QHBoxLayout>
  6. Widget::Widget(QWidget *parent)
  7.   : QWidget(parent)
  8. {
  9.    _myWork = new MyWork;
  10.    _thread = new QThread(this);
  11.    _myWork->moveToThread(_thread);
  12.    _thread->start();
  13.    QPushButton *pb0  = new QPushButton("work", this);
  14.    QPushButton *pb1  = new QPushButton("pb", this);
  15.    QHBoxLayout *hBox = new QHBoxLayout(this);
  16.    hBox->addWidget(pb0);
  17.    hBox->addWidget(pb1);
  18.    this->setLayout(hBox);
  19.    /*发射信号给在另外一个线程的对象的队列中*/
  20.    connect(pb0, SIGNAL(clicked()), _myWork, SLOT(workSlot()));
  21.    connect(pb1, SIGNAL(clicked()), _myWork, SLOT(otherWorkSlot()));
  22.    /*推荐用法释放内存*/
  23.    //connect(_thread, SIGNAL(finished()), _myWork, SLOT(deleteLater()));
  24. }
  25. Widget::~Widget()
  26. {
  27.    _myWork->deleteLater(); //一定要在QThread线程退出之前
  28.    _thread->quit();
  29.    _thread->wait();
  30. }

3 线程的同步

​ 多线程在访问同时一个资源,(例如:多个线程可操作的变量、函数等),到底谁来使用这个资源是一个问题,就像一大群人去抢同一块蛋糕,可能其中一个人抢到,更有可能蛋糕被抢个稀烂。在多线程中,这个叫作竞争冒险。那么我们需要定一个规则来约束每一个人,比如:每个人排队来领蛋糕,这个在多线程中叫作同步方法。

​ 需要注意的是,同步不是同时,而是有序进行。

3.1 互斥锁

​ Qt中的互斥锁是QMutex,不继承任何Qt基类,使用QMutex来锁共享资源,哪个线程抢到钥匙,哪个线程就有这个资源的使用权,其它线程等待这个线程使用完资源并归还钥匙,然后它们再去抢钥匙。

​ 例如:


   
   
  1. QMutex mutex; //这对象一般定义在多个线程能访问的地方
  2. mutex.lock(); //多个线程调用这个函数去获取锁,没有获取到的线程,将阻塞等待在这个函数上。
  3. mutex.unlock(); //释放锁
  • QMutex::lock函数会让线程等待获取锁,如果不想等待,可以使用一下函数替换:


   
   
  1. bool QMutex::tryLock(int timeout = 0)
  2. /*
  3. *参数 int timeout:等到timeout毫秒,不管有没获取到锁都返回,timeout为0时,直接返回。
  4. *返回值 true代表获取到锁,false没有获取到
  5. */
  • 有时候我们会忘记释放锁,Qt还为我们提供了管理锁的类QMutexLocker


   
   
  1. QMutex mutex;
  2. void func()
  3. {
  4.    QMutexLocker locker(_mutex); //QMutexLocker最好实例化成栈对象,释放之前将QMutex解锁。  
  5. }

以上例子,一旦func()执行完成locker自动释放,释放之前先解锁。

3.2 信号量

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


   
   
  1. QSemaphore sem(2) //初始化信号量为2
  2. sem.acquire();  //信号量部位0的时候,调用这个函数会让信号量-1,一旦信号量为零,阻塞等待
  3. semaphore.release(); //使信号量+1

4 定时器

​ 定时器可以隔一段时间发出信号,通过接收这个信号来处理一些定时任务,需要注意的是,定时器并没有开启一个新线程。Qt中的定时器是QTimer继承自QObject。

  • 通过QTimer::start()来启动定时器


   
   
  1. void start(int msec)
  2. /*
  3. *定时msec毫秒后,发射timeout()信号
  4. */

​ 通过链接信号timeout()来处理一些定时任务,例如:


   
   
  1. #include "dialog.h"
  2. #include <QTimer>
  3. #include <qdebug.h>
  4. Dialog::Dialog(QWidget *parent)
  5.   : QDialog(parent)
  6. {
  7. QTimer *timer = new QTimer(this);
  8. connect(timer, SIGNAL(timeout()), this, SLOT(doSomeThing()));
  9.    timer->start(3000);
  10.    
  11. }
  12. void Dialog::doSomeThing()
  13. {
  14.    qDebug() <<  __FUNCTION__ << endl;
  15. }
  16. Dialog::~Dialog()
  17. {
  18. }

发布了207 篇原创文章 · 获赞 66 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/qq_38025219/article/details/104978986
今日推荐