問(wèn)題描述
我有一個(gè) QThread
,它會(huì)定期生成大量數(shù)據(jù)(每秒幾兆字節(jié)),并且需要將其傳輸?shù)礁?(GUI) 線程.
I have a QThread
which generates a fairly large amount of data regularly (couple of megabytes per second), and it needs to transmit it to the parent (GUI) thread.
恐怕我對(duì) QThread
的內(nèi)部運(yùn)作不是很確定,所以我想尋求最佳實(shí)踐.
I'm afraid I'm not that certain in the inner workings of QThread
so I would like to ask for a best practice.
顯然,傳輸數(shù)據(jù)最直接的方式就是emit
一個(gè)數(shù)組.然而,這有多有效?Qt是否知道它的使用位置并避免在發(fā)送和接收時(shí)進(jìn)行深度復(fù)制?
Obviously, the most direct way to transmit data is to just emit
an array. However, how efficient is this? Does Qt know about where it is used and avoids deep copying it when sending and receiving it?
如果沒(méi)有,我很樂(lè)意只在主線程中分配內(nèi)存并提供一個(gè)指向子線程的指針,它將在其中寫(xiě)入數(shù)據(jù)(并且只emit
關(guān)于進(jìn)度的短消息).這對(duì)我來(lái)說(shuō)似乎不是最優(yōu)雅的解決方案,這就是我問(wèn)的原因.
If not, I can gladly just allocate the memory in the main thread and give a pointer to the child thread where it will write the data (and only emit
short messages about the progress). This does not seem to be the most elegant solution to me, this is why I'm asking.
如果Qt在發(fā)送和接收時(shí)避免復(fù)制多個(gè)緩沖區(qū)中的數(shù)據(jù),是否在所有系統(tǒng)中都能保證?我沒(méi)有資源嘗試在各種操作系統(tǒng)下對(duì)其進(jìn)行基準(zhǔn)測(cè)試.
If Qt avoids copying the data in multiple buffers when emitting and receiving, is it guaranteed in all systems? I don't have the resources to try benchmarking it under various OSs.
推薦答案
QThread
的內(nèi)部工作無(wú)關(guān)緊要:它們?cè)谑录h(huán)的工作方式中沒(méi)有任何作用.當(dāng)您在 QObject
中發(fā)出
一個(gè)信號(hào),該信號(hào)位于與插槽對(duì)象不同的線程中時(shí),該信號(hào)將作為 QMetaCallEvent
發(fā)布到接收線程的事件隊(duì)列.然后,在接收線程中運(yùn)行的事件循環(huán)將對(duì)該事件執(zhí)行操作,并在連接到所發(fā)出信號(hào)的槽中執(zhí)行調(diào)用.
QThread
's inner workings are irrelevant: they play no role in how the event loops work. When you emit
a signal in a QObject
that lives in a thread different from the slot's object, the signal will be posted as a QMetaCallEvent
to the event queue of the receiving thread. The event loop running in the receiving thread will then act on this event and execute the call into the slot that was connected to the emitted signal.
因此,無(wú)論發(fā)生什么,您通過(guò)信號(hào)發(fā)送的任何數(shù)據(jù)最終都將作為 QEvent 派生類(lèi)實(shí)例中的有效負(fù)載結(jié)束.
So, no matter what happens, whatever data you send through the signal will eventually end up as a payload in an instance of QEvent-derived class.
問(wèn)題的關(guān)鍵在于當(dāng) QMetaCallEvent
到達(dá)事件循環(huán)并且容器作為參數(shù)被傳遞到槽中時(shí).當(dāng)然,復(fù)制構(gòu)造函數(shù)可以在此過(guò)程中多次調(diào)用.下面是一些簡(jiǎn)單的代碼,演示了復(fù)制構(gòu)造函數(shù)和默認(rèn)構(gòu)造函數(shù)實(shí)際上被調(diào)用了多少次
The meat of the issue is when the QMetaCallEvent
reaches the event loop and the container gets passed into the slot as an argument. Of course the copy constructors could be called plenty of times along the way. Below is some simple code that demonstrates how many times the copy constructor and default constructor are in fact called
關(guān)于隱式共享的寫(xiě)時(shí)復(fù)制容器 (QVector) 的數(shù)據(jù)成員的元素,
on the elements of the data members of an implicitly shared copy-on-write container (QVector),
在代表容器的自定義類(lèi)上.
on a custom class that stands in for a container.
你會(huì)驚喜的 :)
由于 Qt 容器是隱式共享的寫(xiě)時(shí)復(fù)制,因此它們的復(fù)制構(gòu)造成本可以忽略不計(jì):所做的只是引用計(jì)數(shù)器在構(gòu)造時(shí)自動(dòng)遞增.例如,不會(huì)復(fù)制任何數(shù)據(jù)成員.
Since Qt containers are implicitly shared copy-on-write, their copy construction has negligible cost: all that's done is a reference counter is incremented atomically on construction. None of the data members are copied, for example.
唉,11 歲之前的 C++ 顯示了它丑陋的一面:如果槽代碼以任何方式修改了容器,則無(wú)法以這種方式傳遞對(duì)槽的引用,讓編譯器知道原始容器不是不再需要了.因此:如果插槽收到對(duì)容器的 const 引用,則可以保證不會(huì)制作任何副本.如果槽接收到容器的可寫(xiě)副本并且你修改了它,將會(huì)有一個(gè)完全不必要的副本,因?yàn)檎{(diào)用站點(diǎn)上的實(shí)例不再需要.在 C++-11 中,您將傳遞一個(gè)右值引用作為參數(shù).在函數(shù)調(diào)用中傳遞右值引用會(huì)結(jié)束調(diào)用者中傳遞對(duì)象的生命周期.
Alas, pre-11 C++ shows its ugly side: if the slot code modifies the container in any way, there's no way to pass references to the slot in such a way that would let the compiler know that the original container is not needed anymore. Thus: if the slot receives a const reference to the container, you're guaranteed that no copies will be made. If the slot receives a writeable copy of the container and you modify it, there will be a completely unnecessary copy made since the instance alive at the call site is no longer needed. In C++-11 you'd pass an rvalue reference as a parameter. Passing an rvalue reference in a function call ends the lifetime of the passed object in the caller.
示例代碼輸出:
"Started" copies: 0 assignments: 0 default instances: 0
"Created Foo" copies: 0 assignments: 0 default instances: 100
"Created Bar" copies: 0 assignments: 0 default instances: 100
"Received signal w/const container" copies: 0 assignments: 0 default instances: 100
"Received signal w/copy of the container" copies: 0 assignments: 0 default instances: 100
"Made a copy" copies: 100 assignments: 1 default instances: 101
"Reset" copies: 0 assignments: 0 default instances: 0
"Received signal w/const class" copies: 2 assignments: 0 default instances: 1
"Received signal w/copy of the class" copies: 3 assignments: 0 default instances: 1
//main.cpp
#include <QtCore>
class Class {
static QAtomicInt m_copies;
static QAtomicInt m_assignments;
static QAtomicInt m_instances;
public:
Class() { m_instances.fetchAndAddOrdered(1); }
Class(const Class &) { m_copies.fetchAndAddOrdered(1); }
Class & operator=(const Class &) { m_assignments.fetchAndAddOrdered(1); return *this; }
static void dump(const QString & s = QString()) {
qDebug() << s << "copies:" << m_copies << "assignments:" << m_assignments << "default instances:" << m_instances;
}
static void reset() {
m_copies = 0;
m_assignments = 0;
m_instances = 0;
}
};
QAtomicInt Class::m_instances;
QAtomicInt Class::m_copies;
QAtomicInt Class::m_assignments;
typedef QVector<Class> Vector;
Q_DECLARE_METATYPE(Vector)
class Foo : public QObject
{
Q_OBJECT
Vector v;
public:
Foo() : v(100) {}
signals:
void containerSignal(const Vector &);
void classSignal(const Class &);
public slots:
void sendContainer() { emit containerSignal(v); }
void sendClass() { emit classSignal(Class()); }
};
class Bar : public QObject
{
Q_OBJECT
public:
Bar() {}
signals:
void containerDone();
void classDone();
public slots:
void containerSlotConst(const Vector &) {
Class::dump("Received signal w/const container");
}
void containerSlot(Vector v) {
Class::dump("Received signal w/copy of the container");
v[99] = Class();
Class::dump("Made a copy");
Class::reset();
Class::dump("Reset");
emit containerDone();
}
void classSlotConst(const Class &) {
Class::dump("Received signal w/const class");
}
void classSlot(Class) {
Class::dump("Received signal w/copy of the class");
emit classDone();
//QThread::currentThread()->quit();
}
};
int main(int argc, char ** argv)
{
QCoreApplication a(argc, argv);
qRegisterMetaType<Vector>("Vector");
qRegisterMetaType<Class>("Class");
Class::dump("Started");
QThread thread;
Foo foo;
Bar bar;
Class::dump("Created Foo");
bar.moveToThread(&thread);
Class::dump("Created Bar");
QObject::connect(&thread, SIGNAL(started()), &foo, SLOT(sendContainer()));
QObject::connect(&foo, SIGNAL(containerSignal(Vector)), &bar, SLOT(containerSlotConst(Vector)));
QObject::connect(&foo, SIGNAL(containerSignal(Vector)), &bar, SLOT(containerSlot(Vector)));
QObject::connect(&bar, SIGNAL(containerDone()), &foo, SLOT(sendClass()));
QObject::connect(&foo, SIGNAL(classSignal(Class)), &bar, SLOT(classSlotConst(Class)));
QObject::connect(&foo, SIGNAL(classSignal(Class)), &bar, SLOT(classSlot(Class)));
QObject::connect(&bar, SIGNAL(classDone()), &thread, SLOT(quit()));
QObject::connect(&thread, SIGNAL(finished()), &a, SLOT(quit()));
thread.start();
a.exec();
thread.wait();
}
#include "main.moc"
這篇關(guān)于Qt線程之間發(fā)送大量數(shù)據(jù)的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!