22

QThreadかなり大量のデータを定期的に生成する (1 秒あたり数メガバイト) があり、それを親 (GUI) スレッドに送信する必要があります。

の内部の仕組みについては確信が持てないQThreadので、ベストプラクティスをお願いしたいと思います。

明らかに、データを送信する最も直接的な方法は、単なるemit配列です。しかし、これはどのくらい効率的ですか?Qt はどこで使用されているかを認識しており、送受信時にディープ コピーを回避しますか?

そうでない場合は、メイン スレッドにメモリを割り当てて、データを書き込む子スレッドへのポインターを渡すことができます (emit進行状況に関する短いメッセージのみ)。これは私にとって最もエレガントな解決策ではないようです。これが私が尋ねている理由です。

Qtが送信および受信時に複数のバッファにデータをコピーすることを回避する場合、それはすべてのシステムで保証されますか? さまざまな OS でベンチマークを実行するためのリソースがありません。

4

2 に答える 2

41

QThreadの内部動作は関係ありません。イベント ループの動作には関係ありません。スロットのオブジェクトとは異なるスレッドに存在する にシグナルを送信すると、シグナルは受信スレッドのイベント キューに としてポストされemitます。受信スレッドで実行されているイベント ループは、このイベントに基づいて動作し、送信されたシグナルに接続されたスロットへの呼び出しを実行します。QObjectQMetaCallEvent

したがって、何が起こっても、シグナルを介して送信するデータは、最終的には QEvent 派生クラスのインスタンスのペイロードになります。

問題の核心は、QMetaCallEventがイベント ループに到達し、コンテナーが引数としてスロットに渡されるときです。もちろん、途中で何度もコピー コンストラクタを呼び出すことができます。以下は、コピー コンストラクターとデフォルト コンストラクターが実際に呼び出される回数を示す簡単なコードです。

  • 暗黙的に共有されたコピー オン ライト コンテナー (QVector) のデータ メンバーの要素に対して、

  • コンテナの代わりになるカスタム クラス。

あなたはうれしい驚きになるでしょう:)

Qt コンテナーはコピー オン ライトで暗黙的に共有されるため、コピーの作成にかかるコストはごくわずかです。作成時に参照カウンターがアトミックにインクリメントされるだけです。たとえば、データ メンバはコピーされません。

悲しいかな、11 より前の C++ には醜い側面があります。スロット コードが何らかの方法でコンテナーを変更する場合、元のコンテナーがもう必要ないことをコンパイラーに知らせるような方法でスロットへの参照を渡す方法はありません。したがって、スロットがコンテナーへの const 参照を受け取った場合、コピーが作成されないことが保証されます。スロットがコンテナーの書き込み可能なコピーを受け取り、それを変更する、呼び出しサイトで稼働中のインスタンスが不要になるため、完全に不要なコピーが作成されます。C++-11 では、右辺値参照をパラメーターとして渡します。関数呼び出しで右辺値参照を渡すと、呼び出し元で渡されたオブジェクトの有効期間が終了します。

サンプルコード出力:

"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"
于 2012-07-02T03:29:28.050 に答える
6

大きなバッファを通信するときは、プロデューサスレッドのnew()バッファオブジェクトに「従来型」であり、ロードされると、*バッファをコンシューマスレッドにキュー/エミット/何でも、すぐに別のバッファを(同じ*に) buffer var)、データの次のロード用。

問題:GUIスレッドが追いつかない場合、フロー制御手段(たとえば、*バッファーのプールを事前に割り当ててそれらを「循環」させる)を行わない限り、メモリーが暴走します。

私が通常行うことは、ループ内のいくつかのバッファーインスタンス(大きなサーバーでは最大数千)を事前に割り当て、それらのインスタンスをプロデューサー/コンシューマーの「プールキュー」にプッシュすることです。子スレッドがネットワーク接続からバッファにデータをロードする場合は、プールからデータをポップしてロードする必要があります。次に、バッファをキュー/エミット/その他のバッファをコンシューマスレッドにキューイング/エミットし、受信する可能性のあるデータのために別のバッファをポップします。コンシューマスレッドはバッファを取得し、データを処理して、「使用済み」バッファをプールキューにプッシュします。再利用。これにより、フロー制御が提供されます。子スレッドが、コンシューマスレッドが処理できるよりも速くバッファをロードする場合、プールは空であることが検出され、コンシューマスレッドが使用済みのバッファを返すまでブロックされるため、バッファ/メモリの使用を制限します(また、継続的な新規/廃棄を回避し、

プールキューの数を1秒のタイマーでGUIステータスバーにダンプするのが好きです。これにより、バッファーの使用状況を監視できます(リークがあるかどうかをすばやく特定できます:)。

于 2012-07-01T20:48:06.193 に答える