![](/img/trans.png)
[英]What is the QThreadData of a QObject which is created within std::thread or pthread_create
[英]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分支做了一個小小的調查。
QThread
在QThreadPrivate::start
:
unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg)
{
...
thr->run();
finish(arg);
return 0;
}
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.