Отправка большого объема данных между потоками Qt
у меня есть QThread
который генерирует довольно большой объем данных регулярно (пару мегабайт в секунду), и ему нужно передать его родительскому потоку (GUI).
боюсь, я не совсем уверен во внутренней работе QThread
поэтому я хотел бы попросить о лучшей практике.
очевидно, что самый прямой способ передачи данных-просто emit
массив. Однако насколько это эффективно? Знает ли Qt о том, где он используется, и избегает его глубокого копирования, когда отправляете и получаете?
если нет, я с радостью могу просто выделить память в основном потоке и дать указатель на дочерний поток, где он будет записывать данные (и только emit
короткие сообщения о прогрессе). Это не кажется мне самым элегантным решением, вот почему я спрашиваю.
если Qt избегает копирования данных в нескольких буферах при излучении и получении, гарантируется ли это во всех системах? У меня нет ресурсов, чтобы попытаться провести сравнительный анализ. различные OSs.
2 ответов
не имеет значения: они не играют никакой роли в том, как работают циклы событий. Когда ты emit
сигнал в QObject
который живет в потоке, отличном от объекта слота, сигнал будет размещен как QMetaCallEvent
в очередь событий принимающего потока. Цикл событий, запущенный в принимающем потоке, будет действовать на это событие и выполнять вызов в слот, который был подключен к излучаемому сигналу.
Итак, независимо от того, что происходит, какие бы данные вы передача через сигнал в конечном итоге закончится как полезная нагрузка в экземпляре QEvent-производного класса.
суть вопроса-когда QMetaCallEvent
достигает цикла событий, и контейнер передается в слот в качестве аргумента. Конечно, конструкторы копирования могут быть вызваны много раз на этом пути. Ниже приведен простой код, который показывает, сколько раз конструктор копирования и конструктор по умолчанию фактически называются
на элементы из членов данных неявно общего контейнера копирования при записи (QVector),
на пользовательском классе, который стоит в контейнере.
вы будете приятно удивлены :)
поскольку контейнеры Qt неявно являются общими копиями при записи, их конструкция копирования имеет незначительную стоимость: все, что сделано, это счетчик ссылок, увеличивается атомарно при построении. Ни один из членов данных не копируется, для образец.
увы, pre-11 C++ показывает свою уродливую сторону: если код слота каким-либо образом изменяет контейнер, нет способа передать ссылки на слот таким образом, чтобы компилятор знал, что исходный контейнер больше не нужен. Таким образом: если слот получает ссылку const на контейнер, вы гарантируете, что копии не будут сделаны. Если слот получает записываемую копию контейнера и вы измените его, будет полностью ненужная копия, сделанная, так как экземпляр, живой на сайте вызова, больше не нужен. В C++-11 вы передадите ссылку rvalue в качестве параметра. Передача ссылки rvalue в вызове функции завершает время жизни переданного объекта в вызывающем объекте.
пример вывода кода:
"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"
при передаче больших буферов он "традиционный" для новых() буферных объектов в потоке производителя и, при загрузке, очередь/испускает/независимо от того, *буфер в поток потребителя и немедленно новый() другой, (в тот же *буфер var), для следующей загрузки данных.
проблема: если ваш поток GUI не может идти в ногу, вы получите бегство памяти, если вы не примете некоторую меру управления потоком (например. предварительное выделение пула * буферов и их "циркуляция").
Что Я обычно это предварительно выделяет некоторые экземпляры буфера в цикле (до тысяч на большом сервере) и помещает их экземпляры в "очередь пула" производителя-потребителя. Если дочерний поток хочет загрузить данные из какого-либо сетевого подключения в буфер, он должен вытащить один из пула и загрузить его. Затем он может поставить в очередь/испустить/любой буфер в поток потребителя и открыть другой буфер для любых других данных, которые могут войти. Поток потребителя получает буфер, обрабатывает данные и толкает "используемый" буфер вернуться в очередь пула для повторного использования. Это обеспечивает управление потоком: если дочерний поток загружает буферы быстрее, чем поток-потребитель может их обработать, он найдет пул пустым и заблокирует его, пока поток-потребитель не вернет некоторые используемые буферы, поэтому использование буфера/памяти укупорки (а также избегая постоянного создания/dispose или GC на тех языках, которые его поддерживают).
Мне нравится сбрасывать количество очередей пула в строку состояния GUI на таймере 1-sec - это позволяет мне наблюдать за использованием буфера (и быстро определить, если какая-либо утечка:).