Qt
Threading e concorrenza
Ricerca…
Osservazioni
Alcune note che sono già menzionate nei documenti ufficiali qui e qui :
- Se un oggetto ha un genitore, deve essere nello stesso thread del genitore, cioè non può essere spostato in un nuovo thread, né è possibile impostare un genitore su un oggetto se il genitore e l'oggetto vivono in thread diversi
- Quando un oggetto viene spostato su un nuovo thread, anche tutti i relativi figli vengono spostati nel nuovo thread
- Puoi solo spostare oggetti su un nuovo thread. Non puoi trascinarli su un nuovo thread, ovvero puoi solo
moveToThread
dal thread in cui l'oggetto sta attualmente vivendo
Utilizzo di base di QThread
QThread
è un handle per un thread di piattaforma. Ti consente di gestire il thread monitorandone la durata e richiedendo il completamento del lavoro.
Nella maggior parte dei casi, l'ereditarietà dalla classe non è raccomandata. Il metodo di run
predefinito avvia un ciclo di eventi che può inviare eventi agli oggetti che vivono nella classe. Le connessioni dello slot del segnale cross-thread vengono implementate QMetaCallEvent
un oggetto QMetaCallEvent
all'oggetto target.
Un'istanza QObject
può essere spostata su un thread, dove elaborerà i suoi eventi, come eventi timer o chiamate slot / metodo.
Per eseguire il lavoro su un thread, innanzitutto creare la propria classe worker che deriva da QObject
. Quindi spostalo nella discussione. L'oggetto può eseguire automaticamente il proprio codice, ad esempio utilizzando QMetaObject::invokeMethod()
.
#include <QObject>
class MyWorker : public QObject
{
Q_OBJECT
public:
Q_SLOT void doWork() {
qDebug() << "doWork()" << QThread::currentThread();
// and do some long operation here
}
MyWorker(QObject * parent = nullptr) : QObject{parent} {}
};
class MyController : public QObject
{
Q_OBJECT
Worker worker;
QThread workerThread;
public:
MyController() {
worker.moveToThread(&workerThread);
// provide meaningful debug output
workerThread.setObjectName("workerThread");
workerThread.start();
// the thread starts the event loop and blocks waiting for events
}
~MyController() {
workerThread.quit();
workerThread.wait();
}
void operate() {
// Qt::QueuedConnection ensures that the slot is invoked in its own thread
QMetaObject::invokeMethod(&worker, "doWork", Qt::QueuedConnection);
}
};
Se il tuo operatore deve essere effimero e esistere solo mentre il suo lavoro è in esecuzione, è meglio inviare un metodo o un metodo thread-safe per l'esecuzione nel pool di thread tramite QtConcurrent::run
.
QtConcurrent Run
Se trovi la gestione di QThread e di primitive di basso livello come mutex o semafori troppo complessi, lo spazio dei nomi simultaneo di Qt è quello che stai cercando. Include classi che consentono una gestione dei thread di alto livello.
Diamo un'occhiata a Concurrent Run. QtConcurrent::run()
consente di eseguire la funzione in un nuovo thread. Quando vorresti utilizzarlo? Quando hai qualche operazione lunga e non vuoi creare il thread manualmente.
Ora il codice:
#include <qtconcurrentrun.h>
void longOperationFunction(string parameter)
{
// we are already in another thread
// long stuff here
}
void mainThreadFunction()
{
QFuture<void> f = run(longOperationFunction, "argToPass");
f.waitForFinished();
}
Quindi le cose sono semplici: quando abbiamo bisogno di eseguire un'altra funzione in un altro thread, basta chiamare QtConcurrent::run
, passare la funzione ei suoi parametri e il gioco è fatto!
QFuture
presenta il risultato del nostro calcolo asincrono. In caso di QtConcurrent::run
non possiamo annullare l'esecuzione della funzione.
Richiamo di slot da altri thread
Quando un ciclo di eventi Qt viene utilizzato per eseguire operazioni e un utente non-Qt-saavy deve interagire con quel ciclo di eventi, scrivere lo slot per gestire le chiamate regolari da un altro thread può semplificare le cose per gli altri utenti.
main.cpp:
#include "OperationExecutioner.h"
#include <QCoreApplication>
#include <QThread>
int main(int argc, char** argv)
{
QCoreApplication app(argc, argv);
QThread thrd;
thrd.setObjectName("thrd");
thrd.start();
while(!thrd.isRunning())
QThread::msleep(10);
OperationExecutioner* oe = new OperationExecutioner;
oe->moveToThread(&thrd);
oe->doIt1(123,'A');
oe->deleteLater();
thrd.quit();
while(!thrd.isFinished())
QThread::msleep(10);
return 0;
}
OperationExecutioner.h:
#ifndef OPERATION_EXECUTIONER_H
#define OPERATION_EXECUTIONER_H
#include <QObject>
class OperationExecutioner : public QObject
{
Q_OBJECT
public slots:
void doIt1(int argi, char argc);
};
#endif // OPERATION_EXECUTIONER_H
OperationExecutioner.cpp:
#include "OperationExecutioner.h"
#include <QMetaObject>
#include <QThread>
#include <QDebug>
void OperationExecutioner::doIt1(int argi, char argc)
{
if (QThread::currentThread() != thread()) {
qInfo() << "Called from thread" << QThread::currentThread();
QMetaObject::invokeMethod(this, "doIt1", Qt::QueuedConnection,
Q_ARG(int,argi), Q_ARG(char,argc));
return;
}
qInfo() << "Called from thread" << QThread::currentThread()
<< "with args" << argi << argc;
}
OperationExecutioner.pro:
HEADERS += OperationExecutioner.h
SOURCES += main.cpp OperationExecutioner.cpp
QT -= gui