悪いマルチスレッドのためにほぼ/実際に失敗したプロジェクトからどのような教訓を学びましたか?[閉まっている]


11

悪いマルチスレッドのためにほぼ/実際に失敗したプロジェクトからどのような教訓を学びましたか?

フレームワークは、特定のスレッドモデルを課すことがあるため、物事を1桁正しくするのが難しくなります。

私に関しては、最後の障害からまだ回復していないため、そのフレームワークでマルチスレッドに関係することは一切しない方が良いと感じています。

私は、単純な分岐/結合があり、データが一方向にしか移動しない(信号は円形方向に移動できる)マルチスレッドの問題に長けていることがわかりました。

一部の作業は厳密にシリアル化されたスレッド(「メインスレッド」)でのみ実行でき、他の作業はメインスレッド(「ワーカースレッド」)以外のスレッドでのみ実行できるGUIを処理できません。データとメッセージは、N個のコンポーネント間で全方向に移動する必要があります(完全に接続されたグラフ)。

そのプロジェクトを別のプロジェクトに任せたとき、どこにでもデッドロックの問題がありました。2〜3か月後、他の開発者がデッドロックの問題をすべて解決し、顧客に出荷できるようになったと聞きました。不足している知識の一部を見つけることができませんでした。

プロジェクトに関する何か:メッセージID(スレッドに関係なく、別のオブジェクトのメッセージキューに送信できるイベントの意味を表す整数値)の数は数千になります。一意の文字列(ユーザーメッセージ)も約1,000になります。

追加しました

(過去または現在のプロジェクトとは無関係に)別のチームから得た最高の例えは、「データベースにデータを置く」ことでした。(「データベース」は集中化とアトミック更新を指します。)すべてが同じ「メインスレッド」で実行され、すべての非GUIヘビーリフティングが個々のワーカースレッドで実行される複数のビューに断片化されるGUIでは、アプリケーションのデータはデータベースのように動作する単一の場所に格納され、「データベース」が重要なデータ依存関係を含むすべての「アトミック更新」を処理できるようにします。GUIの他のすべての部分は、画面の描画のみを処理します。UIパーツはデータをキャッシュする可能性があり、ユーザーが適切に設計されていれば、ほんの数秒で陳腐化していることに気付かないでしょう。この「データベース」は「ドキュメント」とも呼ばれます ドキュメントビューアーキテクチャ。残念ながら、いや、私のアプリは実際にはすべてのデータをビューに保存します。なぜそうだったのか分かりません。

仲間の貢献者:

(貢献者は実際の/個人的な例を使用する必要はありません。逸話的な例からの教訓は、自分で信頼できると判断された場合も歓迎します。)



「スレッドで考える」ことができるということは、やや才能があり、より良い言葉遣いがないために、学ぶことができるものではありません。私は非常に長い間、並列システムで作業してきた多くの開発者を知っていますが、データが複数の方向に進む必要がある場合、彼らは詰まります。
ドフィック

回答:


13

私のお気に入りのレッスン–非常に苦労して勝ちました!–マルチスレッドプログラムでは、スケジューラはあなたを嫌う卑劣な豚です。物事がうまくいかない場合、それは起こりますが、予期しない形で起こります。何も間違っを取得し、あなたは奇妙な特異なバグを追いかけすることがあります(ので、任意のあなたはタイミングを変更し、あなたに別の実行パターンを与える追加計装)。

これを修正する唯一の正しい方法は、すべてのスレッド処理を厳密に相関させて、それを正しくする小さなコードにロックし、ロックが適切に保持されることを確保することについて非常に保守的です。最も簡単な方法は、非同期でなければならないメッセージング除き、スレッド間でメモリ(または他のリソース)を共有しないことです。これにより、スレッドを意識しないスタイルで他のすべてを記述できます。(ボーナス:クラスター内の複数のマシンへのスケールアウトははるかに簡単です。)


「非同期でなければならないメッセージングを除き、スレッド間でメモリ(または他のリソース)を共有しない」ための+1。
ネマンジャトリフノヴィッチ

1
唯一の方法は?不変のデータ型はどうですか?
アーロンノート

is that in a multithreaded program the scheduler is a sneaky swine that hates you.-いいえ、そうではありません、あなたがそれをするように言ったことを正確に行います:)
mattnz

@Aaronaught:参照によって渡されるグローバル値は、たとえ不変であっても、グローバルGCを必要とし、それがグローバルリソース全体を再導入します。スレッドごとのメモリ管理を使用できることは、多くのグローバルロックを取り除くことができるため便利です。
ドナルドフェローズ

参照によって非基本型の値を渡すことができないわけではありませんが、より高いレベルのロックが必要です(たとえば、「所有者」は、メッセージが戻るまで参照を保持します。または、所有権を譲渡するためのメッセージングエンジンの複雑なコード。または、すべてをマーシャリングし、他のスレッドでアンマーシャリングします。これは非常に遅いです(とにかくクラスターに行くときはそうする必要があります)。追いかけて、メモリをまったく共有しない方が簡単です。
ドナルドフェローズ

6

ここで私が考えることができるいくつかの基本的なレッスンがあります(失敗したプロジェクトからではなく、実際のプロジェクトで見られる実際の問題から):

  • 共有リソースを保持している間、ブロッキング呼び出しを避けるようにしてください。一般的なデッドロックパターンは、スレッドがミューテックスを取得し、コールバックを行い、同じミューテックスでコールバックをブロックすることです。
  • ミューテックス/クリティカルセクションを使用して、共有データ構造へのアクセスを保護します(またはロックのないものを使用します-ただし、独自のものを発明しないでください!)
  • アトミック性を仮定しないでください-アトミックAPI(例えば、InterlockedIncrement)を使用してください。
  • 使用しているライブラリ、オブジェクト、またはAPIのスレッドセーフに関するRTFM。
  • イベント、セマフォなどの利用可能な同期プリミティブを利用します。(しかし、あなたが良い状態にあることを知っているのでそれらを使用するときは細心の注意を払ってください-私はイベントやデータが失われる可能性があるように間違った状態で通知されるイベントの多くの例を見てきました)
  • スレッドは同時および/または任意の順序で実行でき、そのコンテキストはいつでもスレッド間で切り替えることができると仮定します(他の保証を行うOSの場合を除く)。

6
  • GUIプロジェクト全体は、メインスレッドからのみ呼び出す必要があります。基本的に、GUIに単一の(.net)「呼び出し」を入れないでください。マルチスレッドは、低速のデータアクセスを処理する別のプロジェクトで停止する必要があります。

GUIプロジェクトが多数のスレッドを使用している部分を継承しました。問題を与えているだけです。デッドロック、レースの問題、クロススレッドGUI呼び出し...


「プロジェクト」は「アセンブリ」を意味しますか?アセンブリ間のクラスの分散がスレッドの問題をどのように引き起こすかはわかりません。
ニキエ

私のプロジェクトでは、実際にはアセンブリです。ただし、重要なことは、これらのフォルダー内のすべてのコードをメインスレッドから呼び出す必要があることです。例外はありません。
カーラ

この規則は一般的に適用されるとは思わない。はい、GUIコードを別のスレッドから呼び出さないでください。ただし、クラスをフォルダ/プロジェクト/アセンブリに配布する方法は独立した決定です。
ニキエ

1

Java 5以降には、マルチスレッドフォークジョインスタイルプログラムの処理を簡単にするためのエグゼキューターがあります。

それらを使用して、それは多くの痛みを取り除きます。

(そして、はい、これは私がプロジェクトから学んだ:))


1
この回答を他の言語に適用するには、その言語が提供する高品質の並列処理フレームワークを可能な限り使用します。(ただし、フレームワークが本当に素晴らしく、非常に使いやすいかどうかは時間だけが
わかり

1

ハードリアルタイムの組み込みシステムのバックグラウンドがあります。マルチスレッドに起因する問題がないかどうかをテストすることはできません。(時々、存在を確認できます)。コードは証明可能である必要があります。したがって、すべてのスレッドの相互作用に関するベストプラクティスです。

  • #1ルール:KISS-スレッドが必要ない場合、スレッドをスピンしないでください。可能な限りシリアル化します。
  • #2ルール:#1を破らないでください。
  • #3レビューで証明できない場合、それは正しいことです。

ルール1の+1です。別のスレッドが完了するまで最初はブロックするプロジェクトに取り組んでいました-基本的にはメソッド呼び出しです!幸いなことに、私たちはそのアプローチに反対しました。
マイケルK

#3 FTW。ロックのタイミング図や、何故それが時々バラバラになるのかと不思議に思うよりも良いことを証明するために使用するものと何時間も苦労する方が良い。

1

昨年私が取ったマルチスレッドのクラスからの類推は非常に役に立ちました。スレッド同期は、交差点(データ)が同時に2台の車(スレッド)によって使用されるのを保護する信号機のようなものです。多くの開発者が犯す間違いは、必要な信号を正確に把握するのが難しすぎるか危険だと考えるため、街のほとんどでライトを赤くして1台の車を通過させることです。これは、トラフィックが少ない場合はうまく機能する可能性がありますが、アプリケーションが成長するにつれてグリッドロックにつながります。

それは理論的には既に知っていたものですが、そのクラスの後、類推は本当に私に固執し、その後、スレッドの問題を調査し、1つの巨大なキューを見つけるか、変数への書き込み中にどこでも割り込みが無効になることを発見しました2つのスレッドのみが使用されているか、完全に回避するためにリファクタリングできるミューテックスが長時間保持されています。

言い換えれば、最悪のスレッド化の問題のいくつかは、スレッド化の問題を回避しようとする過剰なスキルによって引き起こされます。


0

もう一度やり直してください。

少なくとも私にとって、違いを生み出したのは実践でした。マルチスレッドおよび分散作業を何度も行った後、あなたはそれをただつかむだけです。

デバッグは本当に難しいことだと思います。VSを使用してマルチスレッドコードをデバッグできますが、gdbを使用する必要がある場合、実際には完全に失われます。私のせいでしょう。

もう1つ学習しているのは、ロックのないデータ構造です。

フレームワークを指定すると、この質問は本当に改善されると思います。たとえば、.NETスレッドプールとバックグラウンドワーカーはQThreadとはまったく異なります。プラットフォーム固有の落とし穴が常にいくつかあります。


私は各フレームワークから学ぶべきことがあると信じているので、フレームワークからの話を聞くことに興味があります。
rwong

1
デバッガーは、マルチスレッド環境ではほとんど役に立ちません。
ペムダス

私はすでに問題が何であるかを教えてくれるマルチスレッド実行トレーサーを持っていますが、それを解決する助けにはなりません。私の問題の核心は、「現在の設計によれば、この方法(シーケンス)でオブジェクトXにメッセージXを渡すことができません。巨大なキューに追加する必要があり、最終的に処理されます。 、適切なタイミングでユーザーにメッセージを表示する方法はありません-それは常に時代錯誤的に発生し、ユーザーを非常に混乱させます。プログレスバー、キャンセルボタン、エラーメッセージを追加する必要がある場所それらはある」
rwong

0

下位レベルのモジュールから上位レベルのモジュールへのコールバックは、逆の順序でロックを取得するため、非常に悪いことがわかりました。


コールバックは悪ではありません...スレッドブレーク以外のことを行うという事実は、おそらく悪の根源です。メッセージキューにトークンを送信するだけではなかったコールバックは非常に疑わしいでしょう。
ペムダス

最適化問題の解決(f(x)の最小化など)は、関数f(x)へのポインターを最適化手順に提供することによって実装されることがよくあります。コールバックなしでどうしますか?
quant_dev

1
ダウン票はありませんが、コールバックは悪ではありません。ロックを保持しながらコールバック呼び出すことは悪です。ロックまたはロックするかどうかわからない場合は、ロック内で何も呼び出さないでください。これには、コールバックだけでなく、仮想関数、API関数、他のモジュールの関数(「高レベル」または「低レベル」)も含まれます。
ニキエ

@nikie:コールバック中にロックを保持する必要がある場合は、APIの残りの部分を再入可能(ハード!)に設計する必要があるか、ロックを保持しているという事実がAPIの文書化された部分である必要があります(残念ですが、時にはあなたができることすべて)。
ドナルドフェローズ

@Donal Fellows:コールバック中にロックを保持する必要がある場合、設計上の欠陥があると思います。他に方法がない場合は、はい、必ず文書化してください!バックグラウンドスレッドでコールバックが呼び出されるかどうかをドキュメント化するのと同じように。それはインターフェースの一部です。
ニキエ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.