本文へスキップします。

本文へ

【全Qt】
【全・Qt】SRAロゴ
H1

技術記事:QThread の started() と finished() シグナルの送信についての考察 (後編)

技術記事

QThread の started() と finished() シグナルの送信についての考察 (後編)

(掲載 2025年3月29日)

デバッグ

まえがき

先月の QThread の started() および finished() シグナルについての記事の後編です。単純化したサンプルコードでの確認をします。

QThread の started() シグナルを Controller のスロットに接続

#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>

using namespace std::chrono_literals;

void print(QAnyStringView message) // 【1】
{
    qDebug().noquote().nospace() << message << ": isMainThread() = " << QThread::isMainThread();
}

class Controller : public QObject
{
    Q_OBJECT

public:
    explicit Controller(QObject* parent = nullptr)
        : QObject(parent) {}

public slots:
    void started() { // 【2】
        print(Q_FUNC_INFO);
    }
};

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    QThread thread;
    Controller controller;

    QObject::connect(&thread, &QThread::started, &controller, &Controller::started); // 【3】

    thread.start();

    QTimer::singleShot(1ms, [&] {
                                thread.quit();
                                thread.wait();
                                QCoreApplication::instance()->quit();
                            });

    return app.exec();
}

#include "main.moc"

【1】は動作確認用の関数です。この関数は、メッセージと、メインスレッドであるかどうかを表示します。

【2】QThread の started() シグナルに接続されるスロットです。print() の表示結果から、このスロットがメインスレッドまたはサブスレッドのどちらで実行されているかが確認できます。

【3】QThread の started() シグナルを Controller の started() スロットに接続した後、スレッドの実行を開始します。

実行結果です。

$ ./connectstartedtocontrollerslot
void Controller::started(): isMainThread() = true
$

【2】のスロットはメインスレッドで実行されています。デバッガーで【2】のスロットにブレークポイントを設定して実行すると、バックトレースからメインスレッド のイベントループ経由で【2】のスロットが呼び出されていることが確認できます。QThread の started() シグナルがサブスレッドから送信され、メインスレッドをスレッドアフィニティとする Controller オブジェクトの【2】のスロットを呼び出そうとするため、Qt::QueuedConnection が使用されるのです。

started() シグナルの送信直後に run() の実行が開始されるため、started() シグナルに接続されたスロットの呼び出しはメインスレッドのイベントループにキューイングされていて、まだ呼び出されていない可能性があります。run() の実行がそのスロットの呼び出しと関連している場合には、注意が必要です。

QThread の finished() シグナルを Controller のスロットに接続

#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>

using namespace std::chrono_literals;

void print(QAnyStringView message)
{
    qDebug().noquote().nospace() << message << ": isMainThread() = " << QThread::isMainThread();
}

class Controller : public QObject
{
    Q_OBJECT

public:
    explicit Controller(QObject* parent = nullptr)
        : QObject(parent) {}

public slots:
    void started() {
        print(Q_FUNC_INFO);
    }

    void finished() { // 【1】
        print(Q_FUNC_INFO);
    }
};

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    QThread thread;
    Controller controller;

    QObject::connect(&thread, &QThread::started, &controller, &Controller::started);
    QObject::connect(&thread, &QThread::finished, &controller, &Controller::finished); // 【2】

    thread.start();

    QTimer::singleShot(1ms, [&] { thread.quit(); });
    QTimer::singleShot(20ms, [] { QCoreApplication::instance()->quit(); });

    return app.exec();
}

#include "main.moc"

【1】QThread の finished() シグナルに接続されるスロットです。print() の表示結果から、このスロットがメインスレッドまたはサブスレッドのどちらで実行されているかが確認できます。

【2】QThread の finished() シグナルを Controller の finished() スロットに接続した後、スレッドの実行を開始します。

実行結果です。

$ ./connectfinishedtocontrollerslot
void Controller::started(): isMainThread() = true
void Controller::finished(): isMainThread() = true
$

【1】のスロットはメインスレッドで実行されています。この動作は、先の QThread の started() シグナルと同じです。

finished() シグナルに接続されたスロットの呼び出しは、メインスレッドのイベントループにキューイングされるため、run() の実行終了直後にすぐに呼び出されない可能性があります。稀ではありますが、run() の直後の状態を正確に判断したい場合には注意が必要です。

Controller のシグナルを Worker のスロットに接続

サブスレットをスレッドアフィニティーとする Worker への一般的な処理フローです。

#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>

using namespace std::chrono_literals;

void print(QAnyStringView message)
{
    qDebug().noquote().nospace() << message << ": isMainThread() = " << QThread::isMainThread();
}

class Worker : public QObject
{
    Q_OBJECT

public:
    explicit Worker(QObject* parent = nullptr)
        : QObject(parent) {}

public slots:
    void doWork() { //【1】
        print(Q_FUNC_INFO);
    }
};

class Controller : public QObject
{
    Q_OBJECT

public:
    explicit Controller(QObject* parent = nullptr)
        : QObject(parent) {}

signals:
    void doWork(); //【2】

public slots:
    void started() {
        print(Q_FUNC_INFO);
    }

    void finished() {
        print(Q_FUNC_INFO);
    }

    void operate() { //【3】
        print(Q_FUNC_INFO);
        emit doWork();
    }
};

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    QThread thread;
    Worker worker;
    worker.moveToThread(&thread);
    Controller controller;

    QObject::connect(&thread, &QThread::started, &controller, &Controller::started);
    QObject::connect(&thread, &QThread::finished, &controller, &Controller::finished);
    QObject::connect(&controller, &Controller::doWork, &worker, &Worker::doWork); //【4】

    thread.start();

    QTimer::singleShot(1ms, [&] { controller.operate(); }); //【5】
    QTimer::singleShot(2ms, [&] { thread.quit(); });
    QTimer::singleShot(20ms, [] { QCoreApplication::instance()->quit(); });

    return app.exec();
}

#include "main.moc"

【1】Worker にサブスレッドで処理させるためのスロットです。

【2】Worker への処理依頼の準備ができたことを示すシグナルです。

【3】Controller が処理を要求するスロットです。Worker への処理依頼ができるようになったので doWork() シグナルを送信します。

【4】Controller の doWork() シグナルを、Worker の doWork() スロットに接続し、Worker への処理依頼を呼び出せるようにします。

【5】Controller に処理をさせるためのスロットを直接呼び出して、Worker に処理をさせます。

実行結果です。

$ ./connectcontrollersignaltoworkerslot
void Controller::started(): isMainThread() = true
void Controller::operate(): isMainThread() = true
void Worker::doWork(): isMainThread() = false
void Controller::finished(): isMainThread() = true
$

Worker の doWork() スロットがサブスレッドで実行されていることが確認できます。デバッガーで【1】のスロットにブレークポイントを設定して実行すると、バックトレースからサブスレッドのイベントループ経由で【1】のスロットが呼び出されていることが確認できます。

QThread の started() シグナルを Worker のスロットに接続

スレッド開始時に Worker に開始処理ををさせたい場合を想定しています。

#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>

using namespace std::chrono_literals;

void print(QAnyStringView message)
{
    qDebug().noquote().nospace() << message << ": isMainThread() = " << QThread::isMainThread();
}

class Worker : public QObject
{
    Q_OBJECT

public:
    explicit Worker(QObject* parent = nullptr)
        : QObject(parent) {}

public slots:
    void doWork() { //【1】
        print(Q_FUNC_INFO);
    }
};

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    QThread thread;
    Worker worker;
    worker.moveToThread(&thread);

    QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork); //【2】

    thread.start();

    QTimer::singleShot(1ms, [&] { thread.quit(); });
    QTimer::singleShot(20ms, [] { QCoreApplication::instance()->quit(); });

    return app.exec();
}

#include "main.moc"

【2】で QThread の started() シグナルを【1】の Worker のスロットに接続すると、スレッド開始時にどのようにして Worker のスロットが呼び出されるでしょうか。期待しているのは、Worker のスロットがサブスレッドで実行されることです。

Worker のスロットは、本来、イベントループを経由してサブスレッド上で実行されることを意図して使用されます。しかし、QThread の run() が実行されていないため、QThread の started() シグナルが送信された時点では、まだイベントループは開始されていません。一方、QThread の started() シグナルがサブスレッドで送信されるため、スレッドアフィニティがサブスレッドに設定されている Worker のスロットは、Qt::DirectConnection によりサブスレッド上で実行されるのです。

デバッガーで Worker のスロットにブレークポイントを設定すると、(当然ながら) イベントループを介さずにサブスレッドで Worker のスロットが呼び出されていることが確認できます。

QThread の finished() シグナルを Worker のスロットに接続

スレッド終了時に、Worker のスロットで終了処理を実行させる場合を想定しており、Worker のスロットがサブスレッド上で実行されることを期待しています。

#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>

using namespace std::chrono_literals;

void print(QAnyStringView message)
{
    qDebug().noquote().nospace() << message << ": isMainThread() = " << QThread::isMainThread();
}

class Worker : public QObject
{
    Q_OBJECT

public:
    explicit Worker(QObject* parent = nullptr)
        : QObject(parent) {}

public slots:
    void doWork() {
        print(Q_FUNC_INFO);
    }
};

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    QThread thread;
    Worker worker;
    worker.moveToThread(&thread);

    QObject::connect(&thread, &QThread::finished, &worker, &Worker::doWork); // 【1】

    thread.start();

    QTimer::singleShot(1ms, [&] { thread.quit(); });
    QTimer::singleShot(20ms, [] { QCoreApplication::instance()->quit(); });

    return app.exec();
}

#include "main.moc"

QThread の finished() シグナルはサブスレッドで送信されるため、【1】で QThread の finished() シグナルと接続している場合を除けば、前項と同様の動作をします。

QThread の finished() シグナルを Worker の deleteLater() スロットに接続

スレッドの実行終了時に、Worker オブジェクトを解放する場合です。deleteLater() を呼び出すのは、イベントループに戻った際にメモリを解放させるという意図です。しかし、QThread の finished() シグナルが送信された時点では、イベントループは既に終了しています。

#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>

using namespace std::chrono_literals;

void print(QAnyStringView message)
{
    qDebug().noquote().nospace() << message << ": isMainThread() = " << QThread::isMainThread();
}

class Worker : public QObject
{
    Q_OBJECT

public:
    explicit Worker(QObject* parent = nullptr)
        : QObject(parent) {}

    ~Worker() override { //【1】
        print(Q_FUNC_INFO);
    }

public slots:
    void doWork() {
        print(Q_FUNC_INFO);
    }
};

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    QThread thread;
    const auto worker = new Worker;;
    worker->moveToThread(&thread);

    QObject::connect(&thread, &QThread::finished, worker, &QObject::deleteLater); //【2】

    thread.start();

    QTimer::singleShot(1ms, [&] { thread.quit(); });
    QTimer::singleShot(20ms, [] { QCoreApplication::instance()->quit(); });

    return app.exec();
}

#include "main.moc"

【2】で QThread の finished() シグナルを Worker の deleteLater() スロットに接続し、スレッド実行を開始し、1 ミリ秒後にスレッドを停止させています。

実行結果です。

$ ./connectfinishedtoworkerdeletelater
virtual Worker::~Worker(): isMainThread() = false
$

Worker のデストラクターはサブスレッドで呼び出されています。Worker のスレッドアフィニティがサブスレッドに設定されているため、deleteLater() を呼び出すと、サブスレッドのイベントループが既に終了していることから、イベントループを介さずに Worker オブジェクトが解放されます。

デバッガーで【1】のデストラクターにブレークポイントを設定して実行すると、バックトレースで前述の動作を確認できます。

Worker のデストラクターで何らかの終了処理を行っている場合には、デストラクターがサブスレッドから呼び出されることを考慮する必要があるでしょう。

QObject の deleteLater() の動作説明

QThread の finished() シグナルを deleteLater() スロットに接続した場合、イベントループが実行されていなくてもメモリ解放がうまく行われていました。イベントループの有無による deleteLater() の動作を、別のサンプルコードで確認してみましょう。

まず、イベントループがあるときに deleteLater() を呼び出す場合です。

#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>

using namespace std::chrono_literals;

void print(QAnyStringView message)
{
    qDebug().noquote().nospace() << message << ": isMainThread() = " << QThread::isMainThread();
}

class Object : public QObject
{
    Q_OBJECT

public:
    explicit Object(QObject* parent = nullptr)
        : QObject(parent) {
        print(Q_FUNC_INFO);
    }

    ~Object() {
        print(Q_FUNC_INFO);
    }
};

class Thread : public QThread
{
    Q_OBJECT

public:
    explicit Thread(QObject* parent = nullptr)
        : QThread(parent) {}

protected:
    void run() override { //【1】
        d.object = new Object{};
        QTimer::singleShot(1ms, [&] { d.object->deleteLater(); });

        exec();
    }

private:
    struct {
        Object* object;
    } d;
};

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    Thread thread{};

    QObject::connect(&thread, &QThread::finished,
                     QCoreApplication::instance(), &QCoreApplication::quit);

    thread.start();

    QTimer::singleShot(20ms, [&] {
        thread.quit();
    });

    return app.exec();
}

#include "main.moc"

【1】で Object を new で作成し、1 ミリ秒後に deleteLater() を呼び出すよう にしています。

一般的な使い方で、実行結果は以下のようになります。

$ ./deleteobjectwithineventloop
Object::Object(QObject *): isMainThread() = false
virtual Object::~Object(): isMainThread() = false
$

デバッガーで Object のデストラクターにブレークポイントを設定して実行し、バックトレースを見ると、サブスレッドのイベントループを介してデストラクターが呼び出され、メモリが解放されていることが確認できます。

次に、イベントループがないときに deleteLater() を呼び出す場合です。

#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>

using namespace std::chrono_literals;

void print(QAnyStringView message)
{
    qDebug().noquote().nospace() << message << ": isMainThread() = " << QThread::isMainThread();
}

class Object : public QObject
{
    Q_OBJECT

public:
    explicit Object(QObject* parent = nullptr)
        : QObject(parent) {
        print(Q_FUNC_INFO);
    }

    ~Object() {
        print(Q_FUNC_INFO);
    }
};

class Thread : public QThread
{
    Q_OBJECT

public:
    explicit Thread(QObject* parent = nullptr)
        : QThread(parent) {}

    Object* object() const noexcept {
        return d.object;
    }

protected:
    void run() override { //【1】
        d.object = new Object{};
        exec();
        d.object->deleteLater();
        print("Before sleep.");
        QThread::sleep(5s);
        print("After sleep.");
    }

private:
    struct {
        Object* object;
    } d;
};

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    Thread thread{};

    QObject::connect(&thread, &QThread::finished,
                     QCoreApplication::instance(), &QCoreApplication::quit);

    thread.start();

    QTimer::singleShot(1ms, [&] { thread.quit(); }); //【2】

    return app.exec();
}

#include "main.moc"

【1】の run() の再実装では、Object を new により作成した後、イベントループを実行し、【2】では 1 ミリ秒後にイベントループを終了させています。その後に deleteLater() を呼び出すと、既にイベントループは実行されていません。

実行結果です。

$ ./deleteobjectwithouteventloop
Object::Object(QObject *): isMainThread() = false
Before sleep.: isMainThread() = false
After sleep.: isMainThread() = false
virtual Object::~Object(): isMainThread() = false
$

メモリー解放が行われています。実行中の様子を観察すると、5 秒間のスリープ後にメモリーが解放されていることが確認できます。deleteLater() はイベントループが存在しない場合、スレッド実行の終了時にメモリを解放します。これは、deleteLater() のドキュメントに記載されている通りです。

QThread の create() メソッドの場合の動作説明

QThread の create() メソッドにファンクターを渡すと、渡したファンクターをサブスレッドで実行する QThread オブジェクトが作成されます。このオブジェクトへの必要な処理を実行後に start() を呼び出ストサブスレッドが実行されます。

QThread の create() メソッドは、内部で run() を再実装した QThread のサブクラスを保持し、その run() 内で渡されたファンクターを実行します。ファンクターでイベントループを作成すれば、イベントループを持ったサブスレッドも扱えます。

#include 
#include 
#include 

void print(QAnyStringView message)
{
    qDebug().noquote().nospace() << message << ": isMainThread() = " << QThread::isMainThread();
}

struct WorkerFunctor
{
    void operator()() {
        print(Q_FUNC_INFO);
    }
};

int main(int argc, char** argv)
{
    print(Q_FUNC_INFO);

    QCoreApplication app(argc, argv);

    WorkerFunctor workerFunctor;
    const auto thread = QThread::create(workerFunctor);

    QObject::connect(thread, &QThread::started, [] { print("thread started"); });
    QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
    QObject::connect(thread, &QObject::destroyed, [] {
        print("thread destroyed");
        QCoreApplication::instance()->quit();
    });

    thread->start();

    return app.exec();
}

実行結果です。

$ ./createthread
int main(int, char **): isMainThread() = true
thread started: isMainThread() = false
void WorkerFunctor::operator()(): isMainThread() = false
thread destroyed: isMainThread() = true
$

今までのコード例と同じようにサブスレッドでファンクターが実行されていることが確認できます。

まとめ

QThread の started() および finished() シグナルはサブスレッドから送信され、接続されたスロットは接続方式に応じて異なるスレッドで実行される可能性があります。前回と今回で、その動作を確認できました。

サンプルコード

startedandfinished.zip