我对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);
^
有谁知道出了什么问题?
答案 0 :(得分:0)
将void DrawWindow::alter_text()
更改为void* DrawWindow::alter_text(void*)
并返回pthread_exit(NULL);
。
答案 1 :(得分:0)
TL; DR:你使用pthreads的方式恰恰是使用QThread
的沮丧方式。仅仅因为你使用不同的api并不意味着你正在做的事情是正常的。
QThread
或std::thread
绝对没问题。忘记pthreads:它们不是可移植的,它们的API是C,因此从C ++程序员的角度来看是令人厌恶的,而且你会因为坚持使用pthread而无缘无故地让你的生活变得悲惨。
您真正的问题是您不了解QThread
的问题。有两个:
QThread
和std::thread
都不会被破坏。好的C ++设计要求类在任何时候都是可破坏的。
您无法破坏正在运行的QThread
或std::thread
。您必须先分别通过致电QThread::wait()
或std::thread::join()
确保其已停止。让他们的析构函数做到这一点并不是一个很大的延伸,并且在QThread
的情况下也会停止事件循环。
通常,人们会通过重新实现QThread
方法来使用run
,或者通过在其上运行仿函数来使用std::thread
。当然,这正是您使用pthread的方式:您在专用线程中运行某些功能。 您使用pthreads的方式与使用QThread
的沮丧方式一样糟糕!
在Qt中有多种方式进行多线程处理,你应该了解每种方法的优缺点。
那么,你如何在C ++ / Qt中进行线程化?
首先,请记住线程是昂贵的资源,理想情况下,应用程序中应该没有可用CPU核心数量的线程。在某些情况下,您被迫拥有更多主题,但我们会在何时讨论这种情况。
使用QThread
而不进行子类化。 run()
的默认实现只是旋转一个事件循环,允许对象运行其定时器并接收事件和排队的槽调用。启动该线程,然后移动一些QObject
个实例。实例将在该线程中运行,并且可以执行他们需要完成的任何工作,远离主线程。当然,对象所做的一切都应该是简短的,运行完成的代码,不会阻塞线程。
此方法的缺点是您不太可能利用系统中的所有核心,因为线程数是固定的。对于任何给定的系统,您可能拥有所需的数量,但更有可能是您拥有的数量太少或太多。您也无法控制线程的繁忙程度。理想情况下,他们都应该平等地#34;忙。
使用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();
});
只有当对象不需要花很长时间才能完成它所做的事情时,才应该这样做。例如,它不应该使用计时器,因为只要对象没有完成,它就会占用池中的整个线程。
使用专用线程运行仿函数或方法。 QThread
和std::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();
}