簡體   English   中英

在工作線程中創建的QObject的線程親和性會發生什么變化呢?

[英]What happens to the thread affinity of a QObject created within a worker thread which then terminates?

假設我調用QtConcurrent::run()在一個工作線程中運行一個函數,在該函數中我動態分配幾個QObject(供以后使用)。 由於它們是在工作線程中創建的,因此它們的線程關聯應該是工作線程的線程關聯。 但是,一旦工作線程終止,QObject線程關聯就不再有效。

問題:Qt是自動將QObjects移動到父線程中,還是我們負責在工作線程終止之前將它們移動到有效線程?

QThread沒有記錄在它完成時自動移動任何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);

回想一下, QThread不是一個線程,它管理一個線程。

QThread完成后,它將繼續存在,並且其中的對象繼續存在於其中,但它們不再處理事件。 可以重新啟動QThread (不推薦),此時事件處理將恢復(因此相同的QThread可以管理不同的線程)。

QThread被銷毀時,生活在其中的對象不再具有任何線程關聯。 文檔並不能保證這一點,事實上說: “在刪除QThread之前,必須確保刪除線程中創建的所有對象。”


假設我調用QtConcurrent::run()在一個工作線程中運行一個函數,在該函數中我動態分配幾個QObject(供以后使用)。 由於它們是在工作線程中創建的,因此它們的線程關聯應該是工作線程的線程關聯。 但是,一旦工作線程終止,QObject線程關聯就不再有效。

QThread不會在此方案中終止。 QtConcurrent::run生成的任務完成時,它運行的QThread返回到QThreadPool ,可以通過后續調用QtConcurrent::run重用,生活在QThread QObject繼續存在。

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();

您可能希望在將對象返回到QThreadPool之前手動將對象移出QThread ,或者只是不使用QtConcurrent::run 擁有一個QtConcurrent::run任務構造QObject ,它比任務更長是一個有問題的設計,任務應該是自包含的。 正如@Mike所指出的, QtConcurrent::run使用的QThread沒有事件循環。

但是,一旦工作線程終止,QObject線程關聯就不再有效。

工作線程的函數調用后沒有終止。 使用QtConcurrent::run是在全局線程池(或一些提供的QThreadPool )上執行大量小任務,同時重用線程以避免為每個小任務創建和銷毀線程的開銷。 除了在所有可用內核之間分配計算。

你可以嘗試查看Qt的源代碼,看看如何實現QtConcurrent::run 您將看到它最終調用RunFunctionTaskBase::start ,它實際上調用了QThreadPool::start ,其中QRunnable調用最初傳遞給QtConcurrent::run的函數。

現在,我想的一點是, QThreadPool::start實施通過將QRunnable到一個隊列,然后試圖喚醒線程池中線程(這是一個等待QRunnable是添加到隊列中)。 這里需要注意的是,來自線程池的線程沒有運行事件循環(它們不是按照這種方式設計的),它們只是在隊列中執行QRunnable而已經沒有了(它們以這種方式實現)出於性能原因,顯然)。

這意味着,當您在QtConcurrent::run執行的函數中創建QObject時,您只是創建一個QObject ,它位於一個沒有事件循環的線程中,來自文檔 ,限制包括:

如果沒有運行事件循環,則不會將事件傳遞給對象。 例如,如果在線程中創建QTimer對象但從不調用exec() ,則QTimer將永遠不會發出其timeout()信號。 調用deleteLater()也不起作用。 (這些限制也適用於主線程。)


TL; DR: QtConcurrent::run在全局QThreadPool (或提供的)的線程中運行函數。 這些線程不運行事件循環,它們只是等待QRunnable運行。 因此,生活在來自這些線程的線程中的QObject不會傳遞任何事件。


文檔中 ,他們使用QThread (可能使用事件循環和worker對象)並使用QtConcurrent::run作為兩個獨立的多線程技術。 它們並不意味着混合在一起。 因此, 線程池中沒有工作對象 ,這只是在尋找麻煩。

問題:Qt是自動將QObjects移動到父線程中,還是我們負責在工作線程終止之前將它們移動到有效線程?

我認為看事情的這種方式后,答案是顯而易見的是Qt QObject s轉換任何線程自動。 該文檔已經警告過在沒有事件循環的情況下在QThread使用QObject ,就是這樣。

您可以隨意將它們移動到您喜歡的任何線程。 但請記住, moveToThread()有時會導致問題。 例如,如果移動工作對象涉及移動QTimer

請注意,將重置該對象的所有活動計時器。 計時器首先在當前線程中停止,然后在targetThread中重新啟動(具有相同的間隔)。 因此,在線程之間不斷移動對象可以無限期地推遲計時器事件。


結論:我認為您應該考慮使用自己運行其事件循環的QThread ,並在那里創建工作者QObject而不是使用QtConcurrent 這種方式遠比移動QObject更好,並且可以避免使用當前方法可能產生的許多錯誤。 查看Qt中多線程技術比較表,並選擇最適合您的用例的技術。 如果您只想執行一次調用函數並獲取其返回值,則僅使用QtConcurrent 如果你想與線程永久交互,你應該切換到使用你自己的QThread和worker QObject

Qt會自動將QObject移動到父線程中,還是我們負責在工作線程終止之前將它們移動到有效線程?

,Qt不會自動將QObject移動到父線程中。

這種行為沒有明確記錄,所以我對Qt框架源代碼 ,master分支做了一個小小的調查。

QThreadQThreadPrivate::start

unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg)
{

  ...

  thr->run();

  finish(arg);
  return 0;
}

QThread::terminate()實現:

void QThread::terminate()
{
  Q_D(QThread);
  QMutexLocker locker(&d->mutex);
  if (!d->running)
      return;
  if (!d->terminationEnabled) {
      d->terminatePending = true;
      return;
  }
  TerminateThread(d->handle, 0);
  d->terminated = true;
  QThreadPrivate::finish(this, false);
}

在這兩種情況下,線程終結都在QThreadPrivate::finish

void QThreadPrivate::finish(void *arg, bool lockAnyway)
{
  QThread *thr = reinterpret_cast<QThread *>(arg);
  QThreadPrivate *d = thr->d_func();

  QMutexLocker locker(lockAnyway ? &d->mutex : 0);
  d->isInFinish = true;
  d->priority = QThread::InheritPriority;
  bool terminated = d->terminated;
  void **tls_data = reinterpret_cast<void **>(&d->data->tls);
  locker.unlock();
  if (terminated)
      emit thr->terminated();
  emit thr->finished();
  QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
  QThreadStorageData::finish(tls_data);
  locker.relock();

  d->terminated = false;

  QAbstractEventDispatcher *eventDispatcher = d->data->eventDispatcher;
  if (eventDispatcher) {
      d->data->eventDispatcher = 0;
      locker.unlock();
      eventDispatcher->closingDown();
      delete eventDispatcher;
      locker.relock();
  }

  d->running = false;
  d->finished = true;
  d->isInFinish = false;

  if (!d->waiters) {
      CloseHandle(d->handle);
      d->handle = 0;
  }

  d->id = 0;
}

它將QEvent::DeferredDelete事件發布到清理QObject::deleteLater ,而不是使用QThreadStorageData::finish(tls_data)eventDispatcher刪除的TLS數據。 之后, QObject將不會從此線程接收任何事件,但QObject的線程關聯性保持不變。 看到void QObject::moveToThread(QThread *targetThread)以了解線程關聯性如何變化是很有趣的。

void QThreadPrivate::finish(void *arg, bool lockAnyway)實現表明QThread沒有改變QObject的線程親和性。

雖然這是一個老問題,但我最近問了同樣的問題,並且剛剛使用QT 4.8和一些測試來回答它。

AFAIK您無法使用QtConcurrent :: run函數中的父項創建對象。 我嘗試了以下兩種方法。 讓我定義一個代碼塊,然后我們將通過選擇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設置為this ,則會出現錯誤,因為this將解析為指向生成在主線程中的anInstance對象的指針, 而不是 QtConcurrent為其調度的線程。 你會看到......

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

如果POINTER_TO_THREAD設置為QObject::thread() ,那么您將收到一個錯誤,因為它將解析為anInstance所在的QThread對象,而不是 QtConcurrent為其調度的線程。 你會看到......

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

希望我的測試對其他人有用。 如果有人知道如何獲得QtConcurrent運行方法的QThread的指針,我會很有興趣聽到它!

我不確定Qt是否會自動更改線程關聯。 但即使它確實如此,唯一合理的線程就是主線程。 我會在自己的線程函數結束時推送它們。

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

現在這只有在對象使用發送和接收信號等事件過程時才有意義。

雖然Qt文檔似乎沒有指定您可以通過跟蹤QObject::thread()在線程完成之前和之后返回的內容而找到的行為。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM