軽々しく糸を通すな! 競合状態は、把握するのが大変な場合があります。特に、スレッドの経験があまりない場合は! (警告されました: ここにドラゴンがいます! 大きな毛むくじゃらの非決定論的で信頼性の高い再現が不可能なドラゴンです!)
デッドロックとは何か知っていますか?ライブロックはどうですか?
それは言った...
ckarmann と他の人がすでに提案しているように: ワーク キュー モデルを使用します。CPU コアごとに 1 つのスレッド。 作業を N 個のチャンクに分割します。多くの行のように、チャンクを適度に大きくします。各スレッドが解放されると、次の作業チャンクがキューから取り出されます。
最も単純なIDEALバージョンでは、N 個のコア、N 個のスレッド、および問題の N 個のサブパーツがあり、各スレッドは最初から何をするかを正確に認識しています。
ただし、スレッドの開始/停止のオーバーヘッドのため、実際には通常は発生しません。スレッドがすでに生成され、アクションを待機していることを本当に望んでいます。(たとえば、セマフォを介して。)
ワークキュー モデル自体は非常に強力です。これにより、通常は N スレッド/コア間で正常に並列化されないクイック ソートなどを並列化できます。
コアよりスレッドが多い?オーバーヘッドを浪費しているだけです。各スレッドにはオーバーヘッドがあります。#threads=#cores であっても、完全な Nx スピードアップ ファクターを達成することはできません。
行ごとに 1 つのスレッドは非常に非効率的です! 1 ピクセルあたり 1 スレッド?考えたくもない。(このピクセル単位のアプローチは、古い Cray のようにベクトル化されたプロセッサ ユニットで遊ぶ場合に、より理にかなっていますが、スレッドではそうではありません!)
図書館?あなたのプラットフォームは何ですか?Unix/Linux/g++ では、pthreads とセマフォをお勧めします。(Pthreads は、Microsoft 互換レイヤーを備えた Windows でも使用できます。しかし、うーん。私はそれをあまり信用していません! そこでは、Cygwin の方が適しているかもしれません。)
Unix/Linux では、man :
* pthread_create, pthread_detach.
* pthread_mutexattr_init, pthread_mutexattr_settype, pthread_mutex_init,
* pthread_mutexattr_destroy, pthread_mutex_destroy, pthread_mutex_lock,
* pthread_mutex_trylock, pthread_mutex_unlock, pthread_mutex_timedlock.
* sem_init, sem_destroy, sem_post, sem_wait, sem_trywait, sem_timedwait.
pthread の条件変数が好きな人もいます。しかし、私は常に POSIX 1003.1b セマフォを好みました。それらは、待機を開始する前に別のスレッドにシグナルを送りたい状況を処理します。または、別のスレッドが複数回通知される場所。
ああ、あなた自身にお願いします。スレッド/ミューテックス/セマフォの pthread 呼び出しをいくつかの C++ クラスにラップします。これにより、問題が大幅に簡素化されます。
読み取り専用および書き込み専用の配列をロックする必要がありますか?
正確なハードウェアとソフトウェアに依存します。通常、読み取り専用配列はスレッド間で自由に共有できます。しかし、そうではない場合もあります。
書くことはほとんど同じです。通常、1 つのスレッドだけが特定の各メモリ スポットに書き込みを行っている限り、問題はありません。しかし、そうではない場合もあります!
これらの奇妙なフェンスポストの状況に陥る可能性があるため、書くことは読むことよりも面倒です。多くの場合、メモリはバイトではなくワードとして書き込まれます。あるスレッドが単語の一部を書き込み、別のスレッドが別の部分を書き込む場合、どのスレッドがいつ何を行うかの正確なタイミング (非決定論的など) によっては、非常に予測不可能な結果が生じる可能性があります。
私はそれを安全にプレイします。各スレッドに読み取り領域と書き込み領域の独自のコピーを与えます。完了したら、データをコピーして戻します。もちろん、すべてミューテックスの下にあります。
ギガバイト単位のデータについて話している場合を除き、メモリ ブライトは非常に高速です。その数マイクロ秒のパフォーマンス時間は、デバッグの悪夢に値するものではありません。
ミューテックスを使用してスレッド間で 1 つの共通データ領域を共有すると、衝突/待機中のミューテックスの非効率性が積み重なり、効率が低下します!
ほら、クリーンなデータ境界は、優れたマルチスレッド コードの本質です。境界が明確でない場合、問題が発生します。
同様に、境界上のすべてをミューテックス状態に保つことが不可欠です。そして、ミューテックスされた領域を短く保つために!
同時に複数のミューテックスをロックしないようにしてください。複数のミューテックスをロックする場合は、常に同じ順序でロックしてください!
可能であれば、ERROR-CHECKING または RECURSIVE ミューテックスを使用してください。FAST ミューテックスは、実際の (測定された) 速度の向上がほとんどなく、トラブルを求めているだけです。
デッドロック状態になった場合は、gdb で実行し、ctrl-c を押して各スレッドにアクセスし、バックトレースします。そうすれば、問題を非常に迅速に見つけることができます。(ライブロックはもっと難しいです!)
最後に 1 つの提案: シングルスレッドでビルドしてから、最適化を開始してください。シングルコア システムでは、スレッド化よりも foo[i++]=bar ==> *(foo++)=bar のようなものの方が速度が向上することがあります。
補遺:ミューテックスされた領域を上に短く保つこと について私が言ったことは何ですか? 2 つのスレッドを考えてみましょう: (Mutex クラスのグローバル共有ミューテックス オブジェクトがあるとします)。
/*ThreadA:*/ while(1){ mutex.lock(); printf("a\n"); usleep(100000); mutex.unlock(); }
/*ThreadB:*/ while(1){ mutex.lock(); printf("b\n"); usleep(100000); mutex.unlock(); }
何が起こるか?
私のバージョンの Linux では、1 つのスレッドが継続的に実行され、もう 1 つのスレッドは枯渇します。ごくまれに、mutex.unlock() と mutex.lock() の間でコンテキスト スワップが発生したときに場所が変更されます。
補遺: あなたの場合、これが問題になる可能性は低いです。しかし、他の問題では、特定の作業チャンクが完了するまでにかかる時間を事前に知ることができない場合があります。問題を (4 つの部分ではなく) 100 の部分に分割し、work-queue を使用して 4 つのコアに分割すると、このような不一致が平滑化されます。
ある作業チャンクが完了するまでに別の作業チャンクの 5 倍の時間がかかる場合、最終的にはすべてが均等になります。チャンクが多すぎると、新しい作業チャンクを取得するオーバーヘッドにより、顕著な遅延が生じます。これは、問題固有のバランスをとる行為です。