TL; DR: Способ использования pthreads - это, как правило, обескураженный способ использования QThread
. Просто потому, что вы используете другой api, это не значит, что то, что вы делаете, в порядке.
Во-первых, имейте в виду, что потоки являются дорогостоящими ресурсами, и в идеале у вас должно быть больше потоков в приложении, чем количество доступных ядер процессора. Есть ситуации, когда вы вынуждены иметь больше потоков, но мы обсудим, когда это так.
Использовать QThread
без подкласса.По умолчанию реализация run()
просто объединяет цикл событий, который позволяет объектам запускать свои таймеры и получать события и очереди вызовов слотов. Запустите нить, затем переместите несколько QObject
экземпляров к нему. Экземпляры будут запускаться в этом потоке и могут выполнять любую работу, которая им нужна, вдали от основного потока. Конечно, все, что объекты делают, должно быть коротким, код запуска до завершения, который не блокирует поток.
Недостатком этого метода является то, что вы вряд ли будете использовать все ядра в системе, так как количество потоков фиксировано. Для любой данной системы у вас может быть столько, сколько необходимо, но, скорее всего, у вас будет слишком мало или слишком много. Вы также не можете контролировать, насколько заняты потоки. В идеале, все они должны быть «одинаково» заняты.
QtConcurrent::run
. Это похоже на GCD от Apple. Существует глобальный QThreadPool
. Когда вы запускаете функтор, один поток из пула будет разбужен и будет выполнять функтор. Количество потоков в пуле ограничено количеством ядер, доступных в системе. Использование большего количества потоков, чем это приведет к снижению производительности.
Функторы, которые вы передаете run
, будут выполнять автономные задачи, которые в противном случае блокировали бы графический интерфейс, приводящий к проблемам удобства использования. Например, используйте его для загрузки или сохранения изображения, выполнения куска вычислений и т. Д.
Предположим, вы хотите иметь ответственный графический интерфейс, который загружает множество изображений. Класс Loader
мог выполнять эту работу без блокировки графического интерфейса.
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
требует подкласса. API pthread
похож на std::thread
, за исключением того, что он является C и ужасно опасен по сравнению с API C++.
// 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. Они не выдают никакого способа, чтобы ваш код получал уведомление, когда запрос был завершен. Единственный способ использовать их - запустить блокирующую функцию/метод запроса, которая не возвращается до тех пор, пока запрос не будет выполнен. Предположим теперь, что вы используете базу данных в приложении графического интерфейса, которое вы хотите сохранить отзывчивым. Если вы запускаете запросы из основного потока, графический интерфейс будет блокироваться при каждом запуске запроса базы данных. Учитывая длительные запросы, перегруженная сеть, dev-сервер с откидным кабелем, который заставляет TCP выполнять наравне с sneakernet ... вы столкнулись с огромными проблемами юзабилити.
Таким образом, вы не можете не использовать соединение с базой данных и выполнять запросы базы данных по выделенному потоку, который может быть заблокирован как можно больше.
Даже в этом случае может оказаться полезным использовать в потоке QObject
и прокрутить цикл событий, так как это позволит вам легко ставить в очередь запросы базы данных без необходимости писать собственную потокобезопасную очередь. Цикл событий Qt уже реализует красивую потокобезопасную очередь событий, чтобы вы могли ее использовать. Например, с учетом того, что модуль SQL Qt может использоваться только из одного потока - таким образом, вы не можете подготовить 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();
}
* «QThread ... но есть много жалоб, окружающих его» *. - Что именно вы имеете в виду? QThread работает очень хорошо, и если вы используете сигналы и слоты, вам лучше использовать QThread, чем pthread. – TheDarkKnight
Работа с потоками в родах l сложно, поэтому вы видите больше людей, жалующихся, но QThread прост и прочен. – Marco
** TL; DR ** Просто не делай этого. Интерфейсы 'QThread',' std :: thread' и 'pthread' очень похожи, за исключением того, что' pthread' - ужасная C-мерка, которая не имеет места в коде C++. 'QThread' и' std :: thread', так же как API 'pthread', за исключением того, что вы можете использовать C++, чтобы сделать жизнь проще и стрелять в вашу ногу гораздо сложнее. ** Плохая идея прислушаться к «жалобам» без их полного понимания **! –