QT マルチスレッドシグナルスロットメカニズムの重要なポイント

この文書では次の内容について説明します。

  1. QT信号スロット機構
  2. マルチスレッド環境での qt に関する注意事項
  3. いくつかの QT 経験の知識ポイント

 

 

1)持ち込みポイントの問題

 

この質問は、いつ、どこで、誰が QT シグナルとスロット関数を送信し、誰がそれらを実行するかを理解することに焦点を当てています。

 

この例を過小評価しないでください。著者は「QT で 5 ~ 6 年間働いている」人たちを見てきましたが、この質問をしても、彼らはまだ「王古は左右に言っていた」と答え、彼らは知りません。答え方。これらの人々は QT を使用する初期段階にあるとしか考えられず、核心的な問題の扉にさえ触れていないことが考えられます。

 

この質問に答える前に、いくつかの基本的な知識を紹介する必要があります。

 

2)オブジェクトはどのスレッドに属しているか

 

問題を説明するコード スニペットを示します。
 

class MyThread:public QThread

{

MyThread(){p1 = new A()} //p1对象在旧线程

void run(){p2 = new A()}//p2对象在新线程

}

void mian() {

MyThread thread1; //thread1对象在旧线程中

thread1.start();

}

QT マルチスレッドでは、QThread::run() 関数のみが新しいスレッド内にあります。

 

実行中の新しいオブジェクトは新しいスレッド内にあります。

さらに、コンストラクター内の新しいオブジェクト、つまりスレッド オブジェクト自体は、依然として古いスレッド内にあります。

MyThread のコンストラクターは依然としてメイン スレッドで呼び出されるため、p1 はメイン スレッドにあります。

 

これらの点は非常に重要です。

 

3) 教科書の紛らわしい qt connect の 5 番目のパラメータ

5 番目のパラメーターは、スロット関数が実行されるスレッドを表します。

1) AutoConnection、デフォルトの接続方法。信号とスロット、つまり送信者と受信者が同じスレッドにある場合は、直接接続と同等です。送信者と受信者が異なるスレッドにある場合は、直接接続と同等です。キュー接続。

2) 直接接続 (DirectConnection) 信号が発信されると、すぐにスロット関数が直接呼び出されます。スロット関数のオブジェクトがどのスレッドにあるかに関係なく、スロット関数は常に送信側が配置されているスレッドで実行されます。つまり、スロット関数とシグナル送信側は同じスレッドにあります。

3) キュー接続 (QueuedConnection)、制御が受信者が位置するスレッドのイベント ループに戻ると、スロット関数が呼び出されます。スロット関数は、受信側が配置されているスレッドで実行されます。つまり、スロット関数とシグナル受信側は同じスレッド内にあります。

4) キュー接続をロックする (QueuedConnection)

Qt::BlockingQueuedConnection: スロット関数の呼び出しタイミングは Qt::QueuedConnection と同じですが、シグナル送信後、スロット関数の実行が終了するまで、送信元のスレッドはブロックされます。受信者と送信者が同じスレッド内に存在してはなりません。そうしないと、プログラムがデッドロックしてしまいます。これは、複数のスレッド間の同期が必要な場合に必要になる場合があります。

5) 単一接続 (QueuedConnection)

Qt::UniqueConnection: このフラグは、ビット単位または (|) によって上記の 4 つと組み合わせて使用​​できます。このフラグが設定されている場合、信号とスロットがすでに接続されている場合、繰り返し接続は失敗します。繰り返しの接続を避けるためです

注: ここでの教科書的な記述は曖昧で間違っている「可能性があります」。ここの間違いは後ほど修正させていただきます。

この文を初めて見た人は、「送信者」という概念について混乱していませんか。それとも、ここの説明だけで、冒頭の質問に答えてもらえますか?

 

4) 例に戻ります

 

冒頭の例では、信号とスロットが直接接続されるという新たな制限を追加しました。

 

1) pa はメインスレッドに属します

2) ダイレクト接続を使用する場合

 

シグナルの送信者は誰ですか? それはメインスレッドですか、子スレッドですか、それとも他の「何か」ですか?

 

注: この概念を知らず、左側の 3 つの質問に明確に答えられない場合は、マルチスレッドでのシグナル スロット メカニズムをまだ完全には理解していません。

 

回答: シグナルの送信者はスレッドであり、他の「もの」ではなく、子スレッドです。直接接続方式のため、スロット関数は子スレッドで呼び出されます。

いつ呼ばれますか? 関数ポインタのメソッドと同様に、エミットが送信されると、「関数ポインタ」を呼び出すメソッドと同様に、子スレッドで直接実行されます。

 

5) 教科書に書いてあることを訂正します

5 番目のパラメーターは、スロット関数が実行されるスレッドを表します。

1) デフォルトの接続方法である自動接続 (AutoConnection)。シグナルとスロット、つまり「シグナルを送信するスレッド」と「受信者が配置されているスレッド」が同じスレッドである場合、これは直接接続; 「シグナルを送信するスレッド」の場合 キュー接続に相当する「受信者が配置されているスレッド」を持つスレッドではありません。

2) 直接接続 (DirectConnection) 信号が発信されると、すぐにスロット関数が直接呼び出されます。スロット関数のオブジェクトがどのスレッドにあっても、スロット関数は常に「シグナルを送信するスレッド」で実行されます。つまり、スロット関数と「シグナル送信スレッド」は同じスレッド内にあります。

3) キュー接続 (QueuedConnection)、制御が受信者が位置するスレッドのイベント ループに戻ると、スロット関数が呼び出されます。スロット関数は受信者が配置されているスレッドで実行されます。つまり、スロット関数は「シグナル受信者が配置されているスレッド」と同じスレッド内にあります。

4) キュー接続をロックする (QueuedConnection)

Qt::BlockingQueuedConnection: スロット関数の呼び出しタイミングは Qt::QueuedConnection と同じですが、シグナル送信後、スロット関数の実行が終了するまで「シグナルを送信するスレッド」はブロックされます。「受信者が配置されているスレッド」と「シグナルを送信するスレッド」は同じスレッド内に存在してはなりません。そうしないと、プログラムがデッドロックしてしまいます。これは、複数のスレッド間の同期が必要な場合に必要になる場合があります。

5) 単一接続 (QueuedConnection)

Qt::UniqueConnection: このフラグは、ビット単位または (|) によって上記の 4 つと組み合わせて使用​​できます。このフラグが設定されている場合、信号とスロットがすでに接続されている場合、繰り返し接続は失敗します。それは、繰り返しの接続を避けるためです。

 

 

注意点:

 

「シグナルを送信するスレッド」とは、コード内で発行シグナルが呼び出されたときの実行スレッドを指し、「シグナル オブジェクト」が属するスレッドではありません。

3 つの要素は、スロット関数がどのスレッドで呼び出されるかを決定します。

 

  1. シグナルを送信するために Emit を呼び出したスレッド。
  2. 受信者オブジェクトが存在するスレッド。
  3. connect の 5 番目のパラメーター。

 

注: スロット関数が実行される場所は、接続関数の作成時 (コンパイル時) ではなく、動的に決定されます。

 

6) スロット機能実行時

 

各スレッドには独自のメッセージ イベント ループ キューがあります。

直接接続方法は、関数を直接呼び出す、いわゆる即時実行です。

キュー モード。すぐには実行されません。シングル スレッドとマルチスレッドの状況に分けられます。

シングルスレッド (同じスレッドで送信と受信): メッセージはメッセージ キューに入れられます。スレッドがメッセージ キューに入ると、キュー上のメッセージに対応するスロット関数を順番に実行します。

クロススレッド (異なるスレッドの送信と受信): メッセージは受信側スレッドのキューに入れられます。受信スレッドが実行中 (ブロックされている、スリープしている、または終了している可能性がある) 場合、そのメッセージ キューに入った後、キュー上のメッセージが順番に実行されます。これは、同じスロット関数が再入可能ではないこと、また、それらはすべてキューに入れられて順次実行されるため、再入可能相互排他アクセスを考慮する必要がないことも意味します (postEvents を使用しないように注意してください)無差別に機能するため、この問題については後ほど個別に説明する機会があります)。

 

7) 間違ったデモンストレーション

class Thread :public QThread{

public:

Thread();

Thread(Test *outObj) { m_outObj = outObj; };

virtual ~Thread();

protected:

void run() {

emit m_outObj->sig_test(); }

private:

Test * m_outObj;

};



int main(int argc, char *argv[])

{

QCoreApplication a(argc, argv);

Test *t = new Test;

QObject::connect(t, &Test::sig_test, t, &Test::slot_test, Qt::QueuedConnection);

   Thread *thread = new Thread(t);

t->moveToThread(thread);

thread->start();

return a.exec();

}

 

上記の要素を十分に理解している場合、この例に何か問題はありますか?

 

以下のように分析します。

  1. 上記の例はキュー接続であり、スロット関数は受信者が配置されているスレッド (who) でスロット関数を実行し、スレッドがイベント ループ (when) に戻るまで待機します。
  2. T が moveToThread を呼び出した後、受信者としての t は子スレッドに移動されます。
  3. 子スレッドが start を実行し、run で Emit シグナルが呼び出された後、子スレッドはすぐにはスロット関数を実行せず、子スレッドがイベント ループに戻るまでスロット関数は実行されません。ただし、子スレッドはこの時点で直接終了するため、子スレッドのイベント ループに戻ることはできません。

結局、スロット関数を実行する機会はありません。

 

8) QT マルチスレッドプログラミングに関する注意事項

1 QT メインスレッドをブロックできません。UI はメインスレッド内にあるため、ブロックされるとインターフェイスがスタックします。while() QCoreApplication::processEvents(); qapp->processEvents() を使用してスリープ操作を置き換えます。[重要]

 

2 メイン以外のスレッドは UI コントロールを操作できません。そうしないと、QT がクラッシュします。これは、インターフェイスとロジックを分離する重要な理由でもあります。[重要]

 

3 親子 QObject オブジェクトは同じスレッド内に存在する必要があり、異なるスレッドのオブジェクトを親子関係にすることはできません。

そうしないと、エラーが報告されるか、不明な状況が発生します。[重要]

 

4 公式の優先順位は、moveToThread メソッドを使用し、次に継承された QThread メソッドを使用することです。理解した後も同様です。

 

5 スロット関数はスレッドで実行されることに注意してください。実行スレッドがスリープしてブロックすると、スロット関数は実行できなくなります。スロットが高速になる場合は、ブロックまたはスリープ状態になる可能性のあるスレッドにスロットを入れないでください。[重要]

 

6 共有変数を変更する voldate に注意し、ロックに注意してください。ロックの動作が異なるとスレッドの状態も異なるため、スレッドのビジネス状態に応じてどのロックを使用するかを検討する必要があります。[重要]

 

7 processEvents は注意して使用してください

スレッド (メイン スレッドおよびその他のスレッドを含む) は負荷の高いコンピューティング タスクを実行します。メッセージ イベントが処理されないことを防ぐために、関数内で processEvents が手動で呼び出され、メッセージ イベントが更新される機会が与えられます。インターフェースがフリーズすることはありません。

一方、スロット関数で processEvents を呼び出すことはできません。これにより、現在のスロット関数が「中断」され、メッセージ キュー内で後続のスロット関数が実行され、同じスロット関数の再入力の問題が発生する可能性があります。スロット機能が必要となり、プログラミングの問題が複雑になります。

 

8 手動でリリースする必要がある新しい Qobject オブジェクトはどれですか?

QObject オブジェクトは、親ノードがある場合、手動で削除せずに解放でき、親ノードが解放されるとすべての子ノードが自動的に解放されます。それ以外の場合、親ノードがない場合は、呼び出して手動で解放する必要があります。消去。

QObject を削除すると、オブジェクトは親ノードから削除され、その後、そのすべての子ノードが削除されて解放されます。一部の addChild 操作は、オブジェクトを親ノードにアクティブに追加します。親子関係は必須ではなく、オブジェクト メモリのリサイクルが関係するため、注意する必要があります。

 

  1. エピローグ

最近、長年 QT 開発を行ってきたと主張するプログラマ数名にインタビューしましたが、その中には QT の 5 番目のパラメータについて聞いたことがない、または聞いたことはあっても原理を深く理解していなかった人もいます。これらの人々は、日々の仕事で深く考えていないか、本質にまで踏み込んでいなかったと考えられます。おそらく、こうした本質的な問題に触れる機会のない人が増えているのではないでしょうか。なぜなら、彼らは低レベルのその場での繰り返しを行うのに忙しすぎるからです。

この記事は純粋に「病気を癒し、命を救う」ことを目的として書かれています。私のレベルが低いため、間違いがある場合は、ご指摘いただき、議論してください。

おすすめ

転載: blog.csdn.net/peterbig/article/details/106946291