zoukankan      html  css  js  c++  java
  • 如何正确使用 QThread[转译]

    如何正确使用 QThread

    • 一小段历史
      • 很久以前, 继承QThread并重新实现它的run()函数是QThread多线程唯一推荐方法. 它很直观和易用, 但是在工作线程中使用信号槽机制以及Qt事件循环时, 用户常常使用错误. 因此Qt核心开发人员Bradley T. Hughes推荐使用QObject::moveToThread把worker对象移动到线程中. 一些用户开始反对以前的用法, 而实际上, 这两种用法都可以在QThread的文档中找到.

    QThread::run()是线程入口点

    • 从Qt的文档中, 我们可以看到如下内容
      A QThread instance represents a thread and provides the means to start() a thread, 
      which will then execute the reimplementation of QThread::run(). The run() 
      implementation is for a thread what the main() entry point is for the application.
      /* 译文: QThread 实例代表一个线程并提供 start() 线程的方法,然后该线程将执行 
      QThread::run() 的重新实现。 run() 实现对于线程就像 main() 入口点对于应用程
      序一样。*/
      
    • 由于 QThread::run() 是线程入口点,因此使用用法 1 是相当直观的。

    用法 1-0

    • 要在新线程中运行一些代码,子类化 QThread 并重新实现其 run() 函数。代码如下:
    #include <QtCore>
    
    class Thread : public QThread
    {
    private:
        void run()
        {
            qDebug()<<"From worker thread: "<<currentThreadId();
        }
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        qDebug()<<"From main thread: "<<QThread::currentThreadId();
    
        Thread t;
        QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit()));
    
        t.start();
        return a.exec();
    }
    
    • 输出结果类似下面:
    From main thread:  0x15a8 
    From worker thread:  0x128c
    

    用法 1-1

    • 由于 QThread::run() 是线程入口点,所以很容易理解,所有在 run() 函数中没有被直接调用的代码都不会在工作线程中执行。
    • 在下面的例子中,成员变量 m_stop 将被 stop() 和 run() 访问。 考虑到前者将在主线程中执行,而后者在工作线程中执行,需要mutex或其他必要的方式。
    #if QT_VERSION>=0x050000
    #include <QtWidgets>
    #else
    #include <QtGui>
    #endif
    
    class Thread : public QThread
    {
        Q_OBJECT
    
    public:
        Thread():m_stop(false)
        {}
    
    public slots:
        void stop()
        {
            qDebug()<<"Thread::stop called from main thread: "<<currentThreadId();
            QMutexLocker locker(&m_mutex);
            m_stop=true;
        }
    
    private:
        QMutex m_mutex;
        bool m_stop;
    
        void run()
        {
            qDebug()<<"From worker thread: "<<currentThreadId();
            while (1) {
                {
                QMutexLocker locker(&m_mutex);
                if (m_stop) break;
                }
                msleep(10);
            }
        }
    };
    
    #include "main.moc"
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        qDebug()<<"From main thread: "<<QThread::currentThreadId();
        QPushButton btn("Stop Thread");
        Thread t;
    
        QObject::connect(&btn, SIGNAL(clicked()), &t, SLOT(stop()));
        QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit()));
    
        t.start();
        btn.show();
        return a.exec();
    }
    
    • 输出结果类似下面这样
    From main thread:  0x13a8 
    From worker thread:  0xab8 
    Thread::stop called from main thread:  0x13a8
    
    • 可以看到Thread::stop()是在主线程中执行的

    用法 1-2(错误用法)

    • 虽然上面的例子很容易理解,但是当在工作线程中引入事件系统(或队列连接)时,它就不那么直观了。
    • 例如,如果我们想在工作线程中定期做一些事情,我们应该怎么做?
      • 在 Thread::run() 中创建一个 QTimer
      • 将超时信号连接到 Thread 的槽
    #include <QtCore>
    
    class Thread : public QThread
    {
        Q_OBJECT
    private slots:
        void onTimeout()
        {
            qDebug()<<"Thread::onTimeout get called from? : "<<QThread::currentThreadId();
        }
    
    private:
        void run()
        {
            qDebug()<<"From worker thread: "<<currentThreadId();
            QTimer timer;
            connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
            timer.start(1000);
    
            exec();
        }
    };
    
    #include "main.moc"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        qDebug()<<"From main thread: "<<QThread::currentThreadId();
    
        Thread t;
        t.start();
    
        return a.exec();
    }
    
    • 乍一看,代码看起来不错。 当线程开始执行时,我们设置一个将在当前线程的事件队列中运行的 QTimer。 我们将 onTimeout() 连接到超时信号。 然后我们看下它是否还在线程中
    • 执行结果如下
    From main thread:  0x13a4 
    From worker thread:  0x1330 
    Thread::onTimeout get called from?:  0x13a4 
    Thread::onTimeout get called from?:  0x13a4 
    Thread::onTimeout get called from?:  0x13a4
    
    • 我们惊奇的发现: 它们在主线程而不是工作线程中被调用。
    • 非常有趣(我们将在下一篇博客中讨论这背后发生的事情)
    如何解决这个问题呢
    • 为了让这个 SLOT 在工作线程中工作,有人将 Qt::DirectConnection 传递给 connect() 函数,像下面这样:
    connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()), Qt::DirectConnection);
    
    • 另一些人试图将下面这行代码添加到线程构造函数中。
    moveToThread(this)
    
    • 它们似乎都按预期工作了。 但是...第二种解决方式明显是错误的.

    • 尽管看起来它跑起来了,但很令人费解,这也不是 QThread 设计的初衷(QThread 中的所有实现函数都是希望在新创建的线程中调用,而不是 QThread 的启动线程)

    • 事实上,根据上面的说法,第一种解决方式也是错误的。 作为 Thread 对象的成员 onTimeout(),也应从创建线程中调用。

    • 两种方式都错了, 我们应该怎么做呢?

    用法 1-3

    • 因为 QThread 对象的任何成员都没有设计为从工作线程调用。 所以我们要使用SLOTS就必须创建一个独立的worker对象。
    #include <QtCore>
    
    class Worker : public QObject
    {
        Q_OBJECT
    private slots:
        void onTimeout()
        {
            qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();
        }
    };
    
    class Thread : public QThread
    {
        Q_OBJECT
    
    private:
        void run()
        {
            qDebug()<<"From work thread: "<<currentThreadId();
            QTimer timer;
            Worker worker;
            connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
            timer.start(1000);
    
            exec();
        }
    };
    
    #include "main.moc"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        qDebug()<<"From main thread: "<<QThread::currentThreadId();
    
        Thread t;
        t.start();
    
        return a.exec();
    }
    
    • 执行结果如下:
    From main thread:  0x810 
    From work thread:  0xfac 
    Worker::onTimeout get called from?:  0xfac 
    Worker::onTimeout get called from?:  0xfac 
    Worker::onTimeout get called from?:  0xfac
    
    • 问题终于解决了!
    • 虽然这很完美,但是您可能已经注意到,当在工作线程中使用事件循环 QThread::exec() 时,QThread::run() 中的代码似乎与 QThread 本身无关。
    • 那么我们是否可以将对象创建从 QThread::run() 中移出,同时,它们的槽函数仍然会被 QThread::run() 调用?

    用法 2-0

    • 如果我们只想使用 QThread::exec(),它默认被 QThread::run() 调用,则不需要再对 QThread 进行子类化。
      • 创建一个 Worker 对象
      • 连接信号与槽
      • 将 Worker 对象移动到子线程
      • start线程
    #include <QtCore>
    
    class Worker : public QObject
    {
        Q_OBJECT
    private slots:
        void onTimeout()
        {
            qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();
        }
    };
    
    #include "main.moc"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        qDebug()<<"From main thread: "<<QThread::currentThreadId();
    
        QThread t;
        QTimer timer;
        Worker worker;
    
        QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
        timer.start(1000);
    
        timer.moveToThread(&t);
        worker.moveToThread(&t);
    
        t.start();
    
        return a.exec();
    }
    
    • 结果如下:
    From main thread:  0x1310 
    Worker::onTimeout get called from?:  0x121c 
    Worker::onTimeout get called from?:  0x121c 
    Worker::onTimeout get called from?:  0x121c
    
    • 正如预期的那样,槽函数不在主线程中运行
    • 在这个例子中,QTimer 和 Worker 都被移动到了子线程中。 实际上,不需要将 QTimer 移动到子线程。

    用法 2-1

    • 只需删除 timer.moveToThread(&t) 这行; 从上面的例子中也可以按预期工作。
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        qDebug()<<"From main thread: "<<QThread::currentThreadId();
    
        QThread t;
        QTimer timer;
        Worker worker;
    
        QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
        timer.start(1000);
    
    //    timer.moveToThread(&t);
        worker.moveToThread(&t);
    
        t.start();
    
        return a.exec();
    }
    
    • 不同之处在于:
      在2-0这个例子中:
      • 信号 timeout() 从子线程发出
      • 由于 timer 和 worker 生活在同一个线程中,因此它们的连接类型是直接连接。
      • 槽函数在发出信号的同一个 thead 中被调用。
        在2-1这个例子中:
      • 信号 timeout() 从主线程发出
      • 由于 timer 和 worker 位于不同的线程中,因此它们的连接类型是排队连接。
      • 槽函数在其活动线程(即子线程)中被调用。
    • 多亏了一种称为queued connections的机制,跨不同线程连接信号和槽是安全的。 如果所有跨线程通信都通过queued connections完成,则不再需要采取QMutex等线程安全措施。

    总结

    • 子类化 QThread 并重新实现其 run() 函数很直观,子类化 QThread 仍然有很多完全有效的理由,但是当在工作线程中使用事件循环时,以正确的方式进行操作并不容易。
    • 当事件循环存在时,通过将它们移动到线程来使用工作对象很容易使用,因为它隐藏了事件循环和queued connections的细节。

    参考博文

    原博文地址

    ps: 跨线程的信号和槽连接参数总结

    • Qt::AutoConnection: 如果信号发射和信号接收的对象在同一个线程, 那么默认执行方式为Direct Connection. 否则, 默认执行方式为Queued Connection.
    • Qt::DirectConnection: 信号发射后, 槽立即被调用. 槽函数在信号发送者的线程中执行, 而接收者可能和发送者不在同一个线程.
    • Qt::QueuedConnection: 在控制权返回到接收者线程的事件循环后才调用. 槽函数在接收者的线程中执行. 发送信号后, 槽函数不会立即执行, 而是等接收者的当前函数执行完, 进入事件循环后, 槽函数才会被调用. 多线程环境下, 这个用的最多.
    • Qt::BlockingQueuedConnection: 槽的调用与Queued Connection相同, 但发送者线程会阻塞等待槽函数返回, 如果接收者线程和发送者线程相同, 则不能使用此方式连接, 因为会引起死锁.
    • Qt::UniqueConnection: 单独使用时, 与Auto Connection相同, 但该参数可以防止重复连接, 当重复连接时会连接失败, connect函数返回false. 该参数可以通过按位或(|)与以上四个结合在一起使用。重复连接时会失败, 并返回false.
  • 相关阅读:
    Spark开发-SparkUDAF(二)
    Spark开发-Spark UDAF(一)
    Spark开发-Spark中类型安全UDAF开发示例
    Spark开发_构建TypeSafe的Dataset
    布隆过滤器(Bloom Filter)
    一个 Spark 应用程序的完整执行流程
    Spark的RPC
    Spark调优
    Hbase系列文章
    Flink怎么做到精确一次的?
  • 原文地址:https://www.cnblogs.com/linkyip/p/14863276.html
Copyright © 2011-2022 走看看