web-dev-qa-db-ja.com

Qtでマルチスレッドを使用する場合のイベントループとシグナルスロット処理

QThreadsの使用に問題があり、適切な組み合わせを見つける前にさまざまな組み合わせを検討する必要がありました。ただし、イベントループとシグナルスロット処理に関して、以下に示す4つのケースで実際に何が起こっているのかまだ完全には理解していません。

OUTPUTセクションにコメントをいくつか追加しましたが、ご覧のとおり、観察された動作の原因についての私の仮定が正しいかどうかはわかりません。また、_case 3_が実際のコードで使用される可能性があるものかどうかもわかりません。これが私のテストコードです(ケースごとに_main.cpp_のみが異なります):

worker.h:

_#include <QObject>
#include <QDebug>
#include <QThread>

class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = 0) { this->isRunning_ = false;}
    bool isRunning() const { return isRunning_; }

signals:
    void processingFinished();
    void inProgress();

public slots:
    void process()
    {
        this->isRunning_ = true;
        qDebug() << this << "processing started";
        for (int i = 0; i < 5; i++)
        {
            QThread::usleep(1000);
            emit this->inProgress();
        }
        qDebug() << this << "processing finished";
        this->isRunning_ = false;
        emit this->processingFinished();
    }

private:
    bool isRunning_;
};
_

workermanager.h:

_#include "worker.h"

class WorkerManager : public QObject
{
    Q_OBJECT
public:
    explicit WorkerManager(QObject *parent = 0) :
        QObject(parent) {}

public slots:
    void process()
    {
        QThread *thread = new QThread();
        Worker  *worker = new Worker();

        connect(thread,SIGNAL(started()),worker,SLOT(process()));
        connect(worker,SIGNAL(processingFinished()),this,SLOT(slot1()));
        connect(worker,SIGNAL(inProgress()),this,SLOT(slot2()));
        worker->moveToThread(thread);

        qDebug() << "starting";
        thread->start();
        QThread::usleep(500);
        while(worker->isRunning()) { }
        qDebug() << "finished";
    }

    void slot1() { qDebug() << "slot1"; }
    void slot2() { qDebug() << "slot2"; }
};
_

main.cpp(ケース1-workerManagerの個別のスレッドはありません):

_#include <QCoreApplication>
#include "workermanager.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    WorkerManager* workerManager = new WorkerManager;    
    workerManager->process();
    qDebug() << "end";
    return a.exec();
}
_

出力-_slot1_と_slot2_の両方がa.exec()で呼び出されます(???-メインイベントループを使用しますか?):

_starting 
Worker(0x112db20) processing started 
Worker(0x112db20) processing finished 
finished 
end
slot2 
slot2 
slot2 
slot2 
slot2 
slot1 
_

main.cpp(ケース2-workerManagerは別のスレッドに移動しましたが、スレッドは開始されていません):

_#include <QCoreApplication>
#include "workermanager.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    WorkerManager* workerManager = new WorkerManager;
    QThread *thread = new QThread();   
    workerManager->moveToThread(thread);       
    workerManager->process();
    qDebug() << "end";
    return a.exec();
}
_

出力-_slot1_も_slot2_も呼び出されませんでした-(???スレッドに関連付けられたイベントループはシグナルを受信しますが、スレッドが開始されていないため、スロットは呼び出されませんか?):

_starting 
Worker(0x112db20) processing started 
Worker(0x112db20) processing finished 
finished 
end
_

main.cpp(ケース3-workerManagerが別のスレッドに移動し、スレッドが開始されましたが、workerManager::process()workerManager->process()を介して呼び出されました):

_#include <QCoreApplication>
#include "workermanager.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    WorkerManager* workerManager = new WorkerManager;
    QThread *thread = new QThread();   
    workerManager->moveToThread(thread); 
    thread->start();     
    workerManager->process();
    qDebug() << "end";
    return a.exec();
}
_

出力-Workerがまだprocess()(???)を実行している間に_slot2_が呼び出されました:

_starting 
Worker(0x197bb20) processing started 
slot2 
slot2 
slot2 
slot2 
Worker(0x197bb20) processing finished 
finished 
end 
slot2 
slot1 
_

main.cpp(ケース4-workerManagerは別のスレッドに移動し、スレッドは開始しましたが、workerManager::process()threadからのstarted()シグナルを使用して呼び出されました):

_#include <QCoreApplication>
#include "workermanager.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    WorkerManager* workerManager = new WorkerManager;
    QThread *thread = new QThread();    
    workerManager->moveToThread(thread);
    QObject::connect(thread,SIGNAL(started()),workerManager,SLOT(process()));
    thread->start();
    qDebug() << "end";
    return a.exec();
}
_

OUTPUT-a.exec()(???)に到達した後に処理されたすべてのイベント:

_end 
starting 
Worker(0x7f1d700013d0) processing started 
Worker(0x7f1d700013d0) processing finished 
finished 
slot2 
slot2 
slot2 
slot2 
slot2 
slot1 
_

ご不明な点がありましたらありがとうございます。

11
Moomin

得られたすべての結果は完全に正しいです。これがどのように機能するかを説明しようと思います。

イベントループは、システムイベントとユーザーイベントを処理するQtコードの内部ループです。 a.exec()を呼び出すと、メインスレッドのイベントループが開始されます。別のスレッドのイベントループは、デフォルトで_QThread::run_の実装で開始されます。

Qtは、イベントを処理する時期であると判断すると、イベントハンドラーを実行します。イベントハンドラーが機能している間、Qtは他のイベントを処理する機会がありません(QApplication::processEvents()または他のメソッドによって直接指定されない限り)。イベントハンドラーが終了すると、制御フローはイベントループに戻り、Qtは別のハンドラーを実行して別のイベントを処理できます。

シグナルとスロットは、Qt用語のイベントおよびイベントハンドラーと同じではありません。ただし、スロットはイベントループによってある程度同様に処理されます。コードに制御フローがある場合(main関数など)、他のC++関数と同じように任意のスロットをすぐに実行できます。しかし、Qtがそれを行う場合、それはイベントループからのみ行うことができます。スロットの実行が遅れる可能性がある一方で、信号は常にすぐに送信されることに注意してください。

次に、それぞれの場合に何が起こるかを見てみましょう。

ケース1

_WorkerManager::process_は、プログラムの開始時に直接実行されます。新しいスレッドが開始され、_Worker::process_が新しいスレッドですぐに実行されます。 _WorkerManager::process_は、ワーカーが完了するまで実行を継続し、メインスレッド内の他のすべてのアクション(スロット処理を含む)をフリーズします。 _WorkerManager::process_が終了すると、制御フローは_QApplication::exec_に移動します。 Qtは他のスレッドへの接続を確立し、スロットの呼び出しに関するメッセージを受信し、その結果、それらすべてを呼び出します。

ケース2

Qtはデフォルトで、このオブジェクトが属するスレッド内のオブジェクトのスロットを実行します。メインスレッドは別のスレッドに属しているため、WorkerManagerのスロットを実行しません。ただし、このスレッドは開始されません。そのイベントループは決して終了しません。 _slot1_および_slot2_の呼び出しは、Qtのキューに永久に残され、スレッドの開始を待機します。悲しい話。

ケース3

この場合、_WorkerManager::process_はメインスレッドから直接呼び出すため、メインスレッドで実行されます。その間、WorkerManagerのスレッドが開始されます。そのイベントループが起動され、イベントを待機しています。 _WorkerManager::process_はWorkerのスレッドを開始し、その中で_Worker::exec_を実行します。 WorkerWorkerManagerへのシグナルの送信を開始します。 WorkerManagerのスレッドは、ほぼ即座に適切なスロットの実行を開始します。この時点で、_WorkerManager::slot2_と_WorkerManager::process_が同時に実行されるのは厄介なようです。ただし、少なくともWorkerManagerがスレッドセーフである場合は、まったく問題ありません。 Workerが完了した直後に、_WorkerManager::process_が終了し、a.exec()が実行されますが、処理するものはあまりありません。

ケース4

メイン関数はWorkerManagerのスレッドを起動し、すぐにa.exec()に移動します。その結果、出力の最初の行としてendが生成されます。 a.exec()は何かを処理し、プログラムの実行を保証しますが、別のスレッドに属しているため、WorkerManagerのスロットは実行しません。 _WorkerManager::process_は、イベントループからWorkerManagerのスレッドで実行されます。 Workerのスレッドが開始され、_Worker::process_がWorkerのスレッドからWorkerManagerのスレッドへのシグナルの送信を開始します。残念ながら、後者は_WorkerManager::process_の実行で忙しいです。 Workerが完了すると、_WorkerManager::process_も終了し、WorkerManagerのスレッドはキューに入れられたすべてのスロットをすぐに実行します。

コードの最大の問題は、usleepと無限ループです。 Qtを使用する場合は、これらを使用しないでください。 _Worker::process_でのスリープは、実際の計算の単なるプレースホルダーであることを理解しています。ただし、WorkerManagerからスリープと無限ループを削除する必要があります。 _WorkerManager::slot1_を使用して、Workerの終了を検出します。 GUIアプリケーションを開発する場合、WorkerManagerを別のスレッドに移動する必要はありません。そのすべてのメソッド(スリープなし)は高速に実行され、GUIをフリーズしません。

17
Pavel Strakhov