問題は、イベント ストームを生成するスレッド コードにあります。ループのカウントは非常に高速です。非常に高速であるため、1000 回の反復ごとに信号を送信するという事実はほとんど重要ではありません。最新の CPU では、1000 の整数除算を実行すると、IIRC で 10 マイクロ秒程度かかります。ループが唯一の制限要因である場合、1 秒あたり約 100,000 のピーク レートで信号を発信することになります。パフォーマンスは他の要因によって制限されるため、これは当てはまりません。これについては以下で説明します。
レシーバーが存在するスレッドとは異なるスレッドでシグナルを送信するとどうなるかを理解しましょうQObject
。シグナルは にパッケージ化されQMetaCallEvent
、受信スレッドのイベント キューにポストされます。受信スレッド (ここでは GUI スレッド) で実行されるイベント ループは、 のインスタンスを使用してこれらのイベントに作用しますQAbstractEventDispatcher
。それぞれQMetaCallEvent
が、接続されたスロットへの呼び出しになります。
受信側の GUI スレッドのイベント キューへのアクセスは、QMutex
. Qt 4.8 以降では、QMutex の実装が大幅に高速化されたため、各シグナルの発行がキューのミューテックスのロックにつながるという事実は問題になりそうにありません。残念ながら、イベントはワーカー スレッドのヒープに割り当ててから、GUI スレッドで割り当てを解除する必要があります。多くのヒープ アロケータは、スレッドがたまたま異なるコアで実行された場合にこれが立て続けに発生すると、パフォーマンスが非常に低下します。
最大の問題は GUI スレッドにあります。隠された O(n^2) 複雑さのアルゴリズムがたくさんあるようです! イベント ループは 10,000 イベントを処理する必要があります。これらのイベントは、非常に迅速に配信される可能性が高く、イベント キュー内の連続したブロックに配置されます。イベント ループは、さらにイベントを処理する前に、それらすべてを処理する必要があります。スロットを呼び出すと、多くの高価な操作が発生します。ヒープからの割り当てが解除されるだけでQMetaCallEvent
なく、ラベルが (再描画) をスケジュールし、update()
これにより圧縮可能なイベントがイベント キューに内部的にポストされます。圧縮可能なイベントの投稿は、最悪の場合、イベント キュー全体を反復処理する必要があります。これは、潜在的な O(n^2) 複雑性アクションの 1 つです。おそらく実際にはもっと重要な別のそのようなアクションは、プログレスバーのsetValue
内部呼び出しですQApplication::processEvents()
. これにより、スロットを再帰的に呼び出して、イベント キューから後続のシグナルを配信できます。自分が思っている以上に多くの作業を行っているため、GUI スレッドがロックされます。
スロットを計測し、再帰的に呼び出されるかどうかを確認します。それを行う手っ取り早い方法は
void Widget::setNum(int n)
{
static int level = 0, maxLevel = 0;
level ++;
maxLevel = qMax(level, maxLevel);
ui->label->setNum( n);
ui->progressBar->setValue(n%101);
if (level > 1 && level == maxLevel-1) {
qDebug("setNum recursed up to level %d", maxLevel);
}
level --;
}
GUI スレッドをフリーズさせているのは、QThread の実行ではなく、GUI スレッドに実行させる膨大な量の作業です。コードが無害に見えても。
processEvents と Run-to-Completion コードに関する補足事項
QProgressBar::setValue
呼び出すのは非常に悪い考えだったと思いますprocessEvents()
。それは人々が物事をコーディングする壊れた方法を助長するだけです (実行から完了までの短いコードではなく、継続的にコードを実行します)。processEvents()
呼び出しは呼び出し元に再帰する可能性があるためsetValue
、ペルソナ ノングラタになり、非常に危険な可能性があります。
実行から完了までのセマンティクスを維持しながら継続的なスタイルでコーディングしたい場合は、C++ でそれを処理する方法があります。1 つは、プリプロセッサを活用することです。たとえば、コードは他の回答を参照してください。
もう 1 つの方法は、式テンプレートを使用して、C++ コンパイラに必要なコードを生成させることです。ここでテンプレート ライブラリを活用することをお勧めします。ブースト スピリットには、パーサーを作成していなくても再利用できる適切な実装の出発点があります。
Windows Workflow Foundationは、シーケンシャル スタイル コードを記述しながら、実行から完了までの短いフラグメントとして実行する方法の問題にも取り組んでいます。彼らは、XML で制御の流れを指定することに頼っています。標準の C# 構文を直接再利用する方法はないようです。それらはデータ構造、a-la JSONとしてのみ提供します。必要に応じて、XML とコードベースの WF の両方を Qt に実装するのは簡単です。.NET と C# がプログラムによるコード生成を十分にサポートしているにもかかわらず...