26

QtConcurrent::run()ワーカースレッドで関数を実行する関数を呼び出し、その関数で複数のQObjectを動的に割り当てたとします(後で使用するため)。それらはワーカースレッドで作成されたため、スレッドアフィニティはワーカースレッドのアフィニティである必要があります。ただし、ワーカースレッドが終了すると、QObjectスレッドアフィニティは無効になります。

質問:QtはQObjectを親スレッドに自動的に移動しますか、それともワーカースレッドが終了する前にQObjectを有効なスレッドに移動する責任がありますか

4

6 に答える 6

8

QThread終了時に sを自動的に移動するように文書化QObjectされていないため、そのようなことはないとすでに結論付けることができると思います。このような動作は非常に驚くべきものであり、API の残りの部分とは相容れないものです。

完全を期すために、Qt 5.6 でテストしました。

QObject o;
{
    QThread t;
    o.moveToThread(&t);
    for (int i = 0; i < 2; ++i)
    {
        t.start();
        QVERIFY(t.isRunning());
        QVERIFY(o.thread() == &t);
        t.quit();
        t.wait();
        QVERIFY(t.isFinished());
        QVERIFY(o.thread() == &t);
    }
}
QVERIFY(o.thread() == nullptr);

QThreadaはスレッドではなく、スレッドを管理することを思い出してください。

QThread終了しても存在し続け、そこに存在するオブジェクトは存在し続けますが、イベントを処理しなくなります。を再起動するQThreadことができ (推奨されません)、その時点でイベント処理が再開されます (そのため、同じQThreadスレッドが別のスレッドを管理している可能性があります)。

が破棄されるQThreadと、そこに存在していたオブジェクトはスレッド アフィニティを失います。ドキュメントはこれを保証しておらず、実際には「スレッドで作成されたすべてのオブジェクトが削除される前に、QThread.


QtConcurrent::run()ワーカー スレッドで関数を実行するを呼び出し、その関数で (後で使用するために) いくつかの QObject を動的に割り当てるとします。それらはワーカー スレッドで作成されたので、それらのスレッド アフィニティはワーカー スレッドのアフィニティである必要があります。ただし、ワーカー スレッドが終了すると、QObject スレッド アフィニティは無効になります。

このQThreadシナリオでは、 は終了しません。によって生成されたタスクがQtConcurrent::run終了すると、そのタスクQThreadが実行されていた が に返さQThreadPoolれ、その後の への呼び出しによって再利用される可能性があり、QtConcurrent::runに存在する はそこに存在QObjectQThread続けます。

QThreadPool::globalInstance()->setMaxThreadCount(1);
QObject *o = nullptr;
QThread *t = nullptr;
QFuture<void> f = QtConcurrent::run([&] {
    o = new QObject;
    t = o->thread();
    QVERIFY(t == QThread::currentThread());
});
f.waitForFinished();
QVERIFY(t == o->thread());
QVERIFY(t->isRunning());
f = QtConcurrent::run([=] {
    QVERIFY(t == QThread::currentThread());
});
f.waitForFinished();

オブジェクトを にQThread戻す前に手動で から移動したいQThreadPool場合や、 を使用しない場合がありますQtConcurrent::runQtConcurrent::runタスクよりも長生きするタスク構造s を持つことQObjectは疑わしい設計であり、タスクは自己完結型であるべきです。@Mike が指摘したように、QThread使用される にQtConcurrent::runはイベント ループがありません。

于 2016-10-13T17:23:02.357 に答える
5

ただし、ワーカー スレッドが終了すると、QObject スレッド アフィニティは無効になります。

関数呼び出しの後、ワーカー スレッドは終了しません。使用の要点は、グローバル スレッド プール (または提供されたいくつかの)QtConcurrent::runで多数の小さなタスクを実行し、スレッドを再利用して、これらの小さなタスクごとにスレッドを作成および破棄するオーバーヘッドを回避することです。利用可能なすべてのコアに計算を分散することに加えて。QThreadPool

Qt のソース コードを見て、 がどのようQtConcurrent::runに実装されているかを確認してみてください。最終的に が呼び出されることがわかりますRunFunctionTaskBase::start。これは基本的に、最初に に渡された関数を呼び出す を呼び出しQThreadPool::startます。QRunnableQtConcurrent::run

ここで私が知りたいのはQThreadPool::start、をキューに追加して実装QRunnableし、スレッド プールからスレッドの 1 つを起動しようとすることです (キューに新しいスレッドが追加されるのを待っQRunnableています)。ここで注意すべきことは、スレッド プールのスレッドはイベント ループを実行していない (このように動作するようには設計されていない) ことQRunnableです。明らかにパフォーマンス上の理由)。

QObjectこれは、で実行される関数でを作成している瞬間に、 docsから、イベントループのないスレッドに存在する をQtConcurrent::run作成しているだけであることを意味します。制限は次のとおりです。QObject

イベント ループが実行されていない場合、イベントはオブジェクトに配信されません。たとえばQTimer、スレッドでオブジェクトを作成しても を呼び出さないexec()場合、QTimerはそのtimeout()シグナルを発信しません。通話deleteLater()もダメ。(これらの制限は、メイン スレッドにも適用されます。)


TL;DR: グローバル(または提供されたもの)QtConcurrent::runからのスレッドで関数を実行します。QThreadPoolこれらのスレッドはイベントループを実行せず、 が実行されるQRunnableのを待つだけです。したがって、QObjectこれらのスレッドからのスレッドに住んでいても、イベントは配信されません。


ドキュメントでは、使用QThread(おそらく、イベント ループとワーカー オブジェクト)と使用QtConcurrent::runを 2 つの別個のマルチスレッド テクノロジとして記載しています。それらは一緒に混合することを意図したものではありません。そのため、スレッド プールにワーカー オブジェクトはありません。これは単にトラブルを求めているだけです。

質問: Qt は自動的に QObjects を親スレッドに移動しますか、それともワーカー スレッドが終了する前にそれらを有効なスレッドに移動する責任がありますか?

このように物事を見た後、答えは明らかに、Qt はs をどのスレッドにも自動的に移動しません。ドキュメントでは、イベント ループなしで aQObjectを使用することについて警告されています。それだけです。QObjectQThread

好きなスレッドに自由に移動できます。moveToThread()ただし、場合によっては問題が発生する可能性があることに注意してください。たとえば、ワーカー オブジェクトの移動に の移動が含まれる場合QTimer:

オブジェクトのすべてのアクティブなタイマーがリセットされることに注意してください。タイマーはまず現在のスレッドで停止され、targetThread で (同じ間隔で) 再開されます。その結果、スレッド間でオブジェクトを絶えず移動すると、タイマー イベントが無期限に延期される可能性があります。


結論:イベント ループを実行する独自のものを使用することを検討し、使用する代わりにQThreadworker を作成する必要があると思います。この方法は、 s を移動するよりもはるかに優れており、現在のアプローチを使用することで発生する可能性のある多くのエラーを回避できます。Qt のマルチスレッド テクノロジの比較表を見て、ユース ケースに最適なテクノロジを選択してください。ワンコール関数を実行して戻り値を取得したい場合にのみ使用してください。スレッドとの永続的な対話が必要な場合は、独自のものを worker で使用するように切り替える必要があります。QObjectQtConcurrentQObjectQtConcurrentQThreadQObject

于 2016-10-14T13:13:16.200 に答える
2

これは古い質問ですが、最近同じ質問をし、QT 4.8 といくつかのテストを使用して回答しました。

私の知る限り、 QtConcurrent::run 関数から親を持つオブジェクトを作成することはできません。以下の2つの方法を試しました。コード ブロックを定義してから、POINTER_TO_THREAD を選択して動作を調べます。

いくつかの疑似コードが私のテストを表示します

Class MyClass : public QObject
{
  Q_OBJECT
public:
  doWork(void)
  {
    QObject* myObj = new QObject(POINTER_TO_THREAD);
    ....
  }
}

void someEventHandler()
{
  MyClass* anInstance = new MyClass(this);
  QtConcurrent::run(&anInstance, &MyClass::doWork)
}

潜在的な範囲の問題を無視しています...

POINTER_TO_THREADが に設定されている場合、QtConcurrent がディスパッチしたスレッドではなく、メイン スレッドに存在するオブジェクトへのポインターに解決されるthisため、エラーが発生します。次のようなものが表示されます...thisanInstance

Cannot create children for a parent in another thread. Parent: anInstance, parents thread: QThread(xyz), currentThread(abc)

POINTER_TO_THREADが に設定されている場合、QtConcurrent がディスパッチしたスレッドではなくQObject::thread()、存在する QThread オブジェクトに解決されるため、エラーが発生します。次のようなものが表示されます...anInstance

Cannot create children for a parent in another thread. Parent: QThread(xyz), parents thread: QThread(xyz), currentThread(abc)

私のテストが他の誰かに役立つことを願っています。QtConcurrent がメソッドを実行する QThread へのポインターを取得する方法を誰かが知っている場合は、それを聞いてみたいと思います!

于 2013-10-30T21:20:45.337 に答える
1

Qt がスレッド アフィニティを自動的に変更するかどうかはわかりません。しかし、たとえそうであっても、移動する唯一の合理的なスレッドはメインスレッドです。スレッド化された関数の最後に自分でプッシュします。

myObject->moveToThread(QApplication::instance()->thread());

これは、オブジェクトがシグナルの送受信などのイベント プロセスを使用する場合にのみ問題になります。

于 2011-03-21T21:50:35.977 に答える
1

QObject::thread()Qt ドキュメントでは動作が指定されていないように見えますが、スレッドが終了する前後に何が返されるかを追跡することで見つけることができます。

于 2012-01-21T23:40:30.490 に答える