c++ - qt thread example
Enviando gran cantidad de datos entre hilos Qt (2)
Tengo un QThread
que genera una gran cantidad de datos con regularidad (un par de megabytes por segundo) y necesita transmitirlos al hilo principal (GUI).
Me temo que no estoy tan seguro en el funcionamiento interno de QThread
por lo que me gustaría pedir una mejor práctica.
Obviamente, la forma más directa de transmitir datos es simplemente emit
una matriz. Sin embargo, ¿qué tan eficiente es esto? ¿Qt sabe dónde se usa y evita copiarlo al enviarlo y recibirlo?
Si no, con mucho gusto puedo asignar la memoria en el hilo principal y darle un puntero al hilo secundario donde escribirá los datos (y solo emit
mensajes cortos sobre el progreso). Esta no parece ser la solución más elegante para mí, por eso lo pregunto.
Si Qt evita copiar los datos en múltiples búferes al emitir y recibir, ¿está garantizado en todos los sistemas? No tengo los recursos para probar la evaluación comparativa en varios sistemas operativos.
Cuando se comunican búferes grandes, es ''tradicional'' a los objetos de búfer nuevos () en el subproceso productor y, cuando están cargados, ponen en cola / emiten cualquiera que sea el búfer * para el subproceso del consumidor e inmediatamente nuevo () otro, (en el mismo * búfer var), para la siguiente carga de datos.
Problema: si el subproceso de la GUI no puede mantenerse al día, obtendrá memoria fuera de control a menos que tome alguna medida de control de flujo (por ejemplo, asignando previamente un grupo de * buffers y ''circulando'').
Lo que normalmente hago es pre-asignar algunas instancias de búfer en un bucle (hasta miles en un gran servidor), e insertar sus instancias en una ''cola de grupo'' de productores y consumidores. Si un subproceso secundario desea cargar datos de alguna conexión de red en un búfer, debe abrir uno del grupo y cargarlo. A continuación, puede poner en cola / emitir / lo que sea el búfer para un subproceso del consumidor y abrir otro búfer para obtener más datos. El subproceso del consumidor obtiene el búfer, procesa los datos y empuja el búfer ''usado'' de nuevo a la cola del grupo para reutilizar. Esto proporciona control de flujo: si el subproceso secundario carga los búferes más rápido de lo que el subproceso del consumidor puede procesarlos, encontrará el grupo vacío y se bloqueará en él hasta que el subproceso del consumidor devuelva algunos búferes usados, por lo que el uso de memoria / búfer se limita, y también evitando la continua nueva / disposición, o GC en los idiomas que lo soportan).
Me gusta volcar el conteo de la cola de la agrupación a una barra de estado de la GUI en un temporizador de 1 segundo, esto me permite ver el uso del búfer (y detectar rápidamente si hay alguna fuga :).
El funcionamiento interno de QThread
es irrelevante: no juegan ningún papel en cómo funcionan los bucles de eventos. Cuando emit
una señal en un QObject
que vive en un subproceso diferente del objeto de la ranura, la señal se publicará como un QMetaCallEvent
en la cola de eventos del subproceso receptor. El bucle de eventos que se ejecuta en el subproceso receptor actuará entonces sobre este evento y ejecutará la llamada en la ranura que estaba conectada a la señal emitida.
Por lo tanto, pase lo que pase, cualquier información que envíe a través de la señal terminará como una carga útil en una instancia de la clase derivada de QEvent.
El problema es que cuando QMetaCallEvent
alcanza el bucle de eventos y el contenedor pasa a la ranura como un argumento. Por supuesto, a los constructores de copias se les podría llamar muchas veces en el camino. A continuación se muestra un código simple que demuestra cuántas veces se llaman de hecho al constructor de copia y al constructor predeterminado
en los elementos de los miembros de datos de un contenedor de copia en escritura compartido de forma implícita (QVector),
en una clase personalizada que sustituye a un contenedor.
Te sorprenderás gratamente :)
Dado que los contenedores Qt se comparten implícitamente con copia en escritura, su construcción de copia tiene un costo insignificante: todo lo que se hace es un contador de referencia que se incrementa atómicamente en la construcción. Ninguno de los miembros de datos se copia, por ejemplo.
Lamentablemente, el C11 anterior a 11 muestra su lado feo: si el código de la ranura modifica el contenedor de alguna manera, no hay forma de pasar las referencias a la ranura de manera que el compilador sepa que el contenedor original ya no es necesario. Por lo tanto: si la ranura recibe una referencia constante al contenedor, tiene la garantía de que no se realizarán copias. Si la ranura recibe una copia grabable del contenedor y usted la modifica, se hará una copia completamente innecesaria ya que la instancia con vida en el sitio de la llamada ya no es necesaria. En C ++ - 11 pasarías una referencia rvalue como parámetro. Al pasar una referencia de rvalor en una llamada de función, finaliza la vida útil del objeto pasado en el llamante.
Código de salida de muestra:
"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"