スレッドの基本

スレッドとは?

スレッドとは、プロセスと同じように並行して物事を行うことです。では、スレッドはプロセスとどう違うのだろうか?スプレッドシートで計算をしているとき、同じデスクトップ上でメディアプレーヤーがお気に入りの曲を再生しているかもしれない。これは、2つのプロセスが並行して動作している例です。1つはスプレッドシート・プログラムを実行し、もう1つはメディア・プレーヤーを実行しています。マルチタスクとは、よく知られた用語である。メディアプレーヤーをよく見てみると、1つのプロセスの中で、もう1つのことが並行して進行していることがわかる。メディアプレーヤーがオーディオドライバに音楽を送信している間、すべてのベルとホイッスルを備えたユーザーインターフェイスは常に更新されている。これがスレッドの目的であり、ひとつのプロセス内での並行処理なのだ。

では、並行処理はどのように実装されているのだろうか?シングルコアCPU上での並列処理は、映画における動く映像の錯覚に似た錯覚である。プロセスの場合、この錯覚は、あるプロセスでのプロセッサの作業を非常に短い時間で中断することで生じる。その後、プロセッサは次のプロセスに移る。プロセスを切り替えるには、現在のプログラム・カウンターを保存し、次のプロセッサのプログラム・カウンターをロードする。レジスタや特定のアーキテクチャ、OS固有のデータについても同様の処理を行う必要があるため、これだけでは十分ではない。

つのCPUが2つ以上のプロセスに電力を供給できるように、CPUを1つのプロセスの2つの異なるコードセグメントで実行させることも可能である。プロセスが開始すると、常に1つのコードセグメントを実行するため、プロセスは1つのスレッドを持つと言われる。しかし、プログラムは2つ目のスレッドを開始することを決定することができる。その場合、1つのプロセス内で2つの異なるコード・シーケンスが同時に処理される。同時実行は、プログラム・カウンターとレジスタを繰り返し保存し、次のスレッドのプログラム・カウンターとレジスタをロードすることで、シングルコアCPU上で実現される。アクティブなスレッド間を循環させるのに、プログラムからの協力は必要ない。次のスレッドに切り替わるとき、スレッドはどのような状態であってもよい。

CPU設計の現在のトレンドは、複数のコアを持つことである。典型的なシングルスレッド・アプリケーションは、1つのコアしか利用できない。しかし、複数のスレッドを持つプログラムでは、複数のコアに割り当てることができ、真に同時並行的な方法で物事を進めることができる。その結果、複数のスレッドに作業を分散させることで、マルチコアCPUではコアを追加使用できるため、プログラムの実行速度が大幅に向上する。

GUIスレッドとワーカースレッド

前述したように、各プログラムは起動時に1つのスレッドを持つ。このスレッドは「メインスレッド」と呼ばれます(Qtアプリケーションでは「GUIスレッド」とも呼ばれます)。Qt GUIはこのスレッドで実行されなければなりません。すべてのウィジェットやいくつかの関連クラス、例えばQPixmap は、セカンダリスレッドでは動作しません。セカンダリスレッドは、メインスレッドから処理作業をオフロードするために使用されるため、一般的に「ワーカースレッド」と呼ばれる。

データへの同時アクセス

各スレッドは独自のスタックを持ち、これは各スレッドが独自の呼び出し履歴とローカル変数を持つことを意味する。プロセスとは異なり、スレッドは同じアドレス空間を共有する。次の図は、スレッドの構成要素がメモリ内にどのように配置されているかを示している。非アクティブなスレッドのプログラム・カウンターとレジスタは、通常カーネル空間に保持される。各スレッドにはコードの共有コピーと個別のスタックがある。

"Thread visualization"

つのスレッドが同じオブジェクトへのポインタを持つ場合、両方のスレッドが同時にそのオブジェクトにアクセスする可能性があり、これはオブジェクトの整合性を破壊する可能性がある。同じオブジェクトの2つのメソッドが同時に実行されると、うまくいかないことがたくさんあることは容易に想像できます。

例えば、異なるスレッドに住んでいるオブジェクトが通信する必要がある場合などです。スレッドは同じアドレス空間を使うので、スレッドがデータを交換するのはプロセスよりも簡単で速い。データをシリアライズしてコピーする必要はない。ポインターの受け渡しは可能だが、どのスレッドがどのオブジェクトに触れるかを厳密に調整する必要がある。つのオブジェクトに対する同時実行は防がなければならない。これを実現する方法はいくつかあり、そのいくつかを以下に説明する。

では、何が安全にできるのか?あるスレッドで作成されたオブジェクトはすべて、他のスレッドがそのオブジェクトへの参照を持たず、オブジェクトが他のスレッドと暗黙の結合を持たない限り、そのスレッド内で安全に使用することができる。このような暗黙の結合は、静的メンバ、シングルトン、グローバルデータのように、インスタンス間でデータが共有されている場合に発生する可能性があります。スレッドセーフでリエントラントなクラスや関数の概念に慣れましょう。

スレッドの使用

スレッドには基本的に2つの使用例があります:

  • マルチコア・プロセッサを利用して処理を高速化する。
  • GUIスレッドやその他のタイム・クリティカルなスレッドは、長時間の処理やブロッキング・コールを他のスレッドにオフロードすることで、応答性を維持する。

スレッドの代替を使用する場合

開発者はスレッドに細心の注意を払う必要がある。他のスレッドを起動するのは簡単だが、すべての共有データの一貫性を保つのは非常に難しい。問題は、たまにしか現れなかったり、特定のハードウェア構成でしか現れなかったりするため、見つけるのが難しいことが多い。特定の問題を解決するためにスレッドを作成する前に、可能な代替案を検討すべきである。

代替案コメント
QEventLoop::processEvents()時間のかかる計算中にQEventLoop::processEvents() を繰り返し呼び出すことで、GUIのブロッキングを防ぐことができる。しかし、ハードウェアによってはprocessEvents()の呼び出しが頻繁に発生したり、十分な頻度で発生しなかったりするため、この解決策はうまくスケールしません。
QTimerバックグラウンド処理は、タイマーを使用して将来のある時点でスロットの実行をスケジュールすることで、便利に実行できる場合があります。インターバルが0のタイマーは、処理するイベントがなくなるとすぐにタイムアウトする。
QSocketNotifier QNetworkAccessManager QIODevice::readyRead()これは、低速のネットワーク接続で、1つまたは複数のスレッドで、それぞれブロッキングリードを行う方法の代替となる。ネットワーク・データのチャンクに応答する計算を素早く実行できる限り、このリアクティブ設計はスレッドでの同期待ちよりも優れている。リアクティブ・デザインは、スレッドよりもエラーの発生が少なく、エネルギー効率も高い。多くの場合、パフォーマンスにもメリットがある。

一般的には、安全でテスト済みの経路のみを使用し、アドホックなスレッド概念を導入しないことを推奨する。QtConcurrent モジュールは、プロセッサの全コアに作業を分散させるための簡単なインターフェイスを提供する。スレッディング・コードはQtConcurrent フレームワークの中に完全に隠されているので、細部に気を配る必要はない。ただし、QtConcurrent は実行中のスレッドとの通信が必要な場合には使用できません。また、ブロッキング処理の処理には使用しないでください。

どの Qt スレッド技術を使うべきか?

Qt におけるマルチスレッド技術のページでは、Qt におけるマルチスレッド技術のさまざまなアプローチの紹介と、それらの中からどのように選択するかについてのガイドラインをご覧いただけます。

Qt スレッドの基本

以下のセクションでは、QObject がスレッドとどのように相互作用するか、プログラムが複数のスレッドから安全にデータにアクセスする方法、そして非同期実行がスレッドをブロックせずに結果を生成する方法について説明します。

QObjectとスレッド

Thread affinity でもこの状況は変わりません。Qtのドキュメントでは、いくつかのメソッドをスレッドセーフとしています。postEvent() は注目すべき例です。スレッドセーフなメソッドは、異なるスレッドから同時に呼び出すことができます。

通常メソッドへの同時アクセスがない場合、他のスレッドにあるオブジェクトのスレッドセーフでないメソッドを呼び出すと、同時アクセスが発生するまでに何千回も動作し、予期せぬ動作を引き起こす可能性があります。テスト・コードを書くことは、スレッドの正しさを完全に保証するものではないが、それでも重要である。Linuxでは、ValgrindとHelgrindがスレッド・エラーの検出に役立つ。

データの完全性の保護

マルチスレッド・アプリケーションを書くときには、データの破損を避けるために特別な注意を払わなければならない。スレッドを安全に使用する方法については、スレッドの同期を参照してください。

非同期実行への対処

ワーカースレッドの結果を得る1つの方法は、スレッドの終了を待つことです。しかし多くの場合、ブロッキング待機は受け入れられません。ブロッキング待ちに代わる方法として、ポストされたイベントやキューに入れられたシグナルやスロットを使った非同期結果配信があります。これは、操作の結果が次のソース行に表示されるのではなく、ソースファイルのどこかにあるスロットに表示されるため、一定のオーバーヘッドが発生します。Qtの開発者は、このような非同期動作はGUIアプリケーションで使用されるイベント駆動型プログラミングによく似ているため、扱い慣れています。

Qt にはスレッドを使用する例がいくつか用意されています。簡単な例については、QThreadQThreadPool のクラスリファレンスを参照してください。より高度な例については、スレッドと並行プログラミングの例のページを参照してください。

より深く

スレッドは非常に複雑なテーマです。Qt には、このチュートリアルで紹介したよりも多くのスレッド処理用のクラスがあります。以下の資料は、このテーマをより深く掘り下げるのに役立ちます:

本ドキュメントに含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。