3

マルチスレッドプログラミングは初めてです。この単純なマルチスレッド プログラムを Qt で作成しました。しかし、このプログラムを実行すると GUI がフリーズし、ウィンドウ内をクリックすると、プログラムが応答していないと応答します。これが私のウィジェットクラスです。私のスレッドは整数のカウントを開始し、この数値が 1000 で割り切れる場合にそれを発行します。私のウィジェットでは、単純にこの数値をシグナル スロット メカニズムでキャッチし、ラベルと進行状況バーに表示します。

   Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    MyThread *th = new MyThread;
    connect( th, SIGNAL(num(int)), this, SLOT(setNum(int)));
    th->start();
}


void Widget::setNum(int n)
{
    ui->label->setNum( n);
    ui->progressBar->setValue(n%101);
}

ここに私のスレッドrun()関数があります:

void MyThread::run()
{
    for( int i = 0; i < 10000000; i++){
        if( i % 1000 == 0)
            emit num(i);
    }
}

ありがとう!

4

3 に答える 3

10

問題は、イベント ストームを生成するスレッド コードにあります。ループのカウントは非常に高速です。非常に高速であるため、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# がプログラムによるコード生成を十分にサポートしているにもかかわらず...

于 2012-07-11T13:31:45.330 に答える
2

スレッドを実装した方法では、独自のイベント ループがありません ( を呼び出さないためexec())。run()コードが実際にスレッド内で実行されるのか、GUI スレッド内で実行されるのかはわかりません。

通常、サブクラス化するべきではありませんQThread。残念ながらサブクラス化をまだ推奨しQThreadているQtのドキュメントを読んだため、おそらくそうしたのでしょうQThread。残念ながら、彼らはまだドキュメントを適切に更新していません。

Qt ブログの「You're doing it wrong」を読み、基本的なマルチスレッド システムのセットアップ方法の例として、「Kari」による回答を使用することをお勧めします。

于 2012-07-11T11:10:41.297 に答える
0

But when I run this program it freezes my GUI and when I click inside my window, it responds that your program is not responding.

はい、IMOはスレッドで多くの作業を行っているため、 CPUを使い果たします。通常、プロセスがアプリケーションイベントキュー要求の処理に進行状況を示さないprogram is not responding場合、メッセージがポップアップします。あなたの場合、これは起こります。

したがって、この場合、作業を分割する方法を見つける必要があります。例として、スレッドは100のチャンクで実行され、10000000が完了するまでスレッドを繰り返します。

またQCoreApplication::processEvents()、長時間の操作を実行している場合も確認する必要があります。

于 2012-07-11T06:38:57.520 に答える