在Qt Widget App中使用POSIX线程

时间:2015-07-29 12:16:28

标签: qt pthreads qt-creator

我对Qt和pthread都比较新,但是我正在尝试使用pthread在我正在制作的基本测试应用程序的后台工作。我知道Qt框架有自己的线程框架 - 但是围绕它有很多抱怨所以我想尽可能使用pthread。代码如下

#include "drawwindow.h"
#include "ui_drawwindow.h"
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include "QThread"

pthread_t th1;

DrawWindow::DrawWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::DrawWindow)
{
    ui->setupUi(this);
}

DrawWindow::~DrawWindow()
{
    delete ui;
}

void DrawWindow::on_pushButton_clicked()
{
    pthread_create(&th1, NULL, &DrawWindow::alter_text, NULL);
}

void DrawWindow::alter_text()
{
    while(1)
    {
        ui->pushButton->setText("1");
        QThread::sleep(1);
        ui->pushButton->setText("one");
        QThread::sleep(1);

    }
}

带标题

#ifndef DRAWWINDOW_H
#define DRAWWINDOW_H

#include <QMainWindow>

namespace Ui {
class DrawWindow;
}

class DrawWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit DrawWindow(QWidget *parent = 0);
    ~DrawWindow();
    void alter_text();


private slots:
    void on_pushButton_clicked();

private:
    Ui::DrawWindow *ui;

};

#endif // DRAWWINDOW_H

我收到了错误

 error: cannot convert 'void (DrawWindow::*)()' to 'void* (*)(void*)' for argument '3' to 'int pthread_create(pthread_t*, const pthread_attr_t*, void* (*)(void*), void*)'
 pthread_create(&th1, NULL, &DrawWindow::alter_text, NULL);
                                                         ^

有谁知道出了什么问题?

2 个答案:

答案 0 :(得分:0)

void DrawWindow::alter_text()更改为void* DrawWindow::alter_text(void*)并返回pthread_exit(NULL);

答案 1 :(得分:0)

TL; DR:你使用pthreads的方式恰恰是使用QThread的沮丧方式。仅仅因为你使用不同的api并不意味着你正在做的事情是正常的。

QThreadstd::thread绝对没问题。忘记pthreads:它们不是可移植的,它们的API是C,因此从C ++程序员的角度来看是令人厌恶的,而且你会因为坚持使用pthread而无缘无故地让你的生活变得悲惨。

您真正的问题是您不了解QThread的问题。有两个:

  1. QThreadstd::thread都不会被破坏。好的C ++设计要求类在任何时候都是可破坏的。

    您无法破坏正在运行的QThreadstd::thread。您必须先分别通过致电QThread::wait()std::thread::join()确保其已停止。让他们的析构函数做到这一点并不是一个很大的延伸,并且在QThread的情况下也会停止事件循环。

  2. 通常,人们会通过重新实现QThread方法来使用run,或者通过在其上运行仿函数来使用std::thread。当然,这正是您使用pthread的方式:您在专用线程中运行某些功能。 您使用pthreads的方式与使用QThread的沮丧方式一样糟糕!

    在Qt中有多种方式进行多线程处理,你应该了解每种方法的优缺点。

  3. 那么,你如何在C ++ / Qt中进行线程化?

    首先,请记住线程是昂贵的资源,理想情况下,应用程序中应该没有可用CPU核心数量的线程。在某些情况下,您被迫拥有更多主题,但我们会在何时讨论这种情况。

    1. 使用QThread而不进行子类化。 run()的默认实现只是旋转一个事件循环,允许对象运行其定时器并接收事件和排队的槽调用。启动该线程,然后移动一些QObject个实例。实例将在该线程中运行,并且可以执行他们需要完成的任何工作,远离主线程。当然,对象所做的一切都应该是简短的,运行完成的代码,不会阻塞线程。

      此方法的缺点是您不太可能利用系统中的所有核心,因为线程数是固定的。对于任何给定的系统,您可能拥有所需的数量,但更有可能是您拥有的数量太少或太多。您也无法控制线程的繁忙程度。理想情况下,他们都应该平等地#34;忙。

    2. 使用QtConcurrent::run。这类似于Apple的GCD。全球QThreadPool。运行仿函数时,池中的一个线程将被唤醒并执行仿函数。池中的线程数限制为系统上可用的核心数。使用更多的线程会降低性能。

      传递给run的仿函数将执行自包含的任务,否则会阻止GUI导致可用性问题。例如,使用它来加载或保存图像,执行一系列计算等。

      假设您希望拥有一个可以加载大量图像的负责任的GUI。 Loader类可以在不阻止GUI的情况下完成工作。

      class Loader : public QObject {
        Q_OBJECT
      public:
        Q_SIGNAL void hasImage(const QImage &, const QString & path);
        explicit Loader(const QStringList & imagePaths, QObject * parent = 0) :
          QObject(parent) {
          QtConcurrent::map(imagePaths, [this](const QString & path){
            QImage image;
            image.load(path);
            emit hasImage(image, path);
          });
        }
      };
      

      如果您希望在线程池的线程中运行短暂的QObject,则仿函数可以按如下方式旋转事件循环:

      auto foo = QSharedPointer<Object>(new Object); // Object inherits QObject
      foo->moveToThread(0); // prepares the object to be moved to any thread
      QtConcurrent::run([foo]{
        foo->moveToThread(QThread::currentThread());
        QEventLoop loop;
        QObject::connect(foo, &Object::finished, &loop, &QEventLoop::quit);
        loop.exec();
      });
      

      只有当对象不需要花很长时间才能完成它所做的事情时,才应该这样做。例如,它不应该使用计时器,因为只要对象没有完成,它就会占用池中的整个线程。

    3. 使用专用线程运行仿函数或方法。 QThreadstd::thread之间的差异主要在于std::thread允许您使用仿函数,而QThread则需要子类化。 pthread API类似于std::thread,当然除了它是C并且与C ++ API相比非常不安全。

      // QThread
      int main() {
        class MyThread : public QThread {
          void run() { qDebug() << "Hello from other thread"; }
        } thread;
        thread.start();
        thread.wait();
        return 0;
      }
      
      // std::thread
      int main() {
        // C++98
        class Functor {
          void operator()() { qDebug() << "Hello from another thread"; }
        } functor;
        std::thread thread98(functor);
        thread98.join();
        // C++11
        std::thread thread11([]{ qDebug() << "Hello from another thread"; });
        thread11.join();
        return 0;
      }
      
      // pthread
      extern "C" void* functor(void*) { qDebug() << "Hello from another thread"; }
      
      int main() 
      {
        pthread_t thread;
        pthread_create(&thread, NULL, &functor, NULL);
        void * result;
        pthread_join(thread, &result);
        return 0;
      }
      

      那么,这有什么好处?有时,您别无选择,只能使用阻止API。例如,大多数数据库驱动程序都具有仅阻止API。当查询完成时,它们无法让您的代码得到通知。使用它们的唯一方法是运行阻塞查询函数/方法,该函数/方法在查询完成之前不会返回。现在假设您在GUI应用程序中使用了希望保持响应的数据库。如果您从主线程运行查询,GUI将在每次运行数据库查询时阻止。鉴于长时间运行的查询,拥挤的网络,带有片状电缆的开发服务器使TCP与sneakernet相提并论......您将面临巨大的可用性问题。

      因此,您不能在其上运行数据库连接,并在可以根据需要阻止的专用线程上执行数据库查询。

      即便如此,在线程上使用一些QObject并旋转事件循环可能仍然有用,因为这样可以轻松地对数据库请求进行排队,而无需编写自己的线程安全队列。 Qt的事件循环已经实现了一个漂亮的,线程安全的事件队列,所以你也可以使用它。例如,请注意Qt的SQL模块只能在一个线程中使用 - 因此您无法在主线程中准备QSQLQuery :(

      请注意,此示例非常简单,您可能希望提供迭代查询结果的线程安全方式,而不是一次性推送整个查询的数据。

      class DBWorker : public QObject {
        Q_OBJECT
        QScopedPointer<QSqlDatabase> m_db;
        QScopedPointer<QSqlQuery> m_qBooks, m_query2;
        Q_SLOT void init() {
          m_db.reset(new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE")));
          m_db->setDatabaseName(":memory:");
          if (!m_db->open()) { emit openFailed(); return; }
          m_qBooks.reset(new QSqlQuery(*m_db));
          m_qBooks->prepare("SELECT * FROM Books");
          m_qCars.reset(new QSqlQuery(*m_db));
          m_qCars->prepare("SELECT * FROM Cars");
        }
        QList<QVariantList> read(QSqlQuery * query) {
          QList<QVariantList> result;
          result.reserve(query->size());    
          while (query->next()) {
            QVariantList row;
            auto record = query->record();
            row.reserve(record.count());
            for (int i = 0; i < recourd.count(); ++i)
              row << query->value(i);
            result << row;
          }
          return result;
        }
      public:
        typedef QList<QVariantList> Books, Cars;
        DBWorker(QObject * parent = 0) : QObject(parent) {
          QObject src;
          connect(&src, &QObject::destroyed, this, &DBWorker::init, Qt::QueuedConnection);
          m_db.moveToThread(0
        }
        Q_SIGNAL void openFailed();
        Q_SIGNAL void gotBooks(const DBWorker::Books &);
        Q_SIGNAL void gotCars(const DBWorker::Cars &);
        Q_SLOT void getBooks() {
          Q_ASSERT(QThread::currentThread() == thread());
          m_qBooks->exec();
          emit gotBooks(read(m_qBooks));
        }
        Q_SLOT void getCars() {
          Q_ASSERT(QThread::currentThread() == thread());
          m_qCars->exec();
          emit gotCars(read(m_qCars));
        }
      };
      Q_REGISTER_METATYPE(DBWorker::Books);
      Q_REGISTER_METATYPE(DBWorker::Cars);
      
      // True C++ RAII thread.
      Thread : public QThread { using QThread::run; public: ~Thread() { quit(); wait(); } };
      int main(int argc, char ** argv) {
        QCoreApplication app(argc, argv);
        Thread thread;
        DBWorker worker;
        worker.moveToThread(&thread);
        QObject::connect(&worker, &DBWorker::gotCars, [](const DBWorker::Cars & cars){
          qDebug() << "got cars:" << cars;
          qApp->quit();
        });
        thread.start();
        ...
        QMetaObject::invokeMethod(&worker, "getBooks"); // safely invoke `getBooks`
        return app.exec();
      }