マルチスレッドが難しい理由を説明する方法


84

私はかなり良いプログラマーであり、上司もかなり良いプログラマーです。彼はマルチスレッドなどのいくつかのタスクを過小評価しているように見えますが、それがどれほど難しいかはわかりません(いくつかのスレッドを実行し、すべてが完了するのを待ってから結果を返す以上のことは非常に難しいと思います)。

デッドロックや競合状態を心配し始めた瞬間、私はそれを非常に難しいと感じますが、上司はこれを感謝していないようです-私は彼がこれに遭遇したことはないと思います。ロックをたたくだけで、態度はほぼ変わりません。

それでは、どうやって彼を紹介したり、なぜ彼が並行性、並列処理、マルチスレッドの複雑さを過小評価しているのかを説明できますか?または多分私は間違っていますか?

編集:彼がやったことのほんの少し-リストをループし、そのリストの各アイテムに対して、そのアイテムの情報に基づいてデータベース更新コマンドを実行するスレッドを作成します。一度に実行されるスレッドの数を彼がどのように制御したかはわかりません。実行中のスレッドが多すぎる場合(キューはセマフォを使用しなかった場合)、キューに追加する必要があります。


17
マルチスレッドは簡単です。正しい同期は困難です。
ビネレイノルズ

33
できれば異なるアクセントで3人を部屋に連れて行き、並行性の問題の異なる重複する部分を同時に説明させます。
-greyfade

マルチスレッドは、目前の問題と言語サポートに応じて、非常に困難または非常に簡単になります。Clojureで簡単にclojure.org/concurrent_programming
ジョブ

4
@Jobコンカレントプログラミングは、(実際のプロジェクトでは)どんな言語を使用していても常に困難です。Scala、Clojure、Erlangは、可変状態を使用および奨励する言語と比較したい場合に、少し正気にします。
シロン

4
これに対する私のお気に入りの比phorは、「睡眠薬と下剤を同時に服用しますか?」です。複雑なメッセージキューを使用する場合でも、順序は並行性の成果です。それは、あなたが持っていない限り素晴らしいそれに経験の契約を、あるハード多くの人々のために。
ティムポスト

回答:


29
  1. 数学的経験を頼りにできる場合は、本質的に決定論的な通常の実行フローが、複数のスレッドで非決定論的になるだけでなく、指数関数的に複雑になることを説明してください。紛失した更新またはダーティリードの状況の簡単な例は、しばしば目を見張るものです。

  2. 「ロックを平手打ち」簡単な解決策です。パフォーマンスを気にしない場合、すべての問題を解決します。たとえば、アトランタの誰かが1冊の本を注文するたびにAmazonが東海岸全体をロックしなければならなかった場合、どれだけのパフォーマンスヒットが発生するかを説明してください。


1
数学的な複雑さの議論のために+1-これは私が共有状態の並行性の難しさを理解するようになった方法であり、メッセージパッシングアーキテクチャを提唱する際に私が一般的に行う議論です。-1 for "slap a lock on ..."というフレーズは、ロックの使用に対する考えられないアプローチを意味します。これは、デッドロックや一貫性のない動作につながる可能性が非常に高いです(異なるスレッドに存在するコードのクライアントが競合するため)クライアント間で同期しませんが、クライアントはライブラリの状態の互換性のないモデルを持ちます)。
エイダンカリー

2
Amazon 、注文の処理中に1つの倉庫で個々のアイテムの在庫を短時間ロックする必要があります。特定のアイテムで突然の大規模な実行が発生した場合、そのアイテムの注文パフォーマンスは、供給が枯渇して在庫へのアクセスが読み取り専用になる(したがって100%共有可能になる)まで低下します。Amazonが他のプログラムにはないことで行っていることの1つは、再在庫が発生するまで注文をキューに入れる機能と、再注文が新しい注文で利用可能になる前にキュー注文を処理するオプションです。
Blrfl

@Blrfl:プログラムは、キューを介したメッセージの受け渡しを使用するように作成されている場合、それを行うことができます。特定のスレッドへのすべてのメッセージを単一のキューを経由させる必要はありません…
ドナルフェローズ

4
@Donal Fellows:1つの倉庫に1Mのウィジェットがあり、1Mの注文が同じ瞬間に到着する場合、それらのリクエストはすべて、あるレベルでシリアル化されます。現実には、Amazonには在庫があまり多くないため、在庫がなくなると注文を処理する際の待ち時間が容認できないほど長くなり、列に並んでいる他の全員に(並行して)「私たちは外に出ます」と伝えることができます。 」メッセージキューはデッドロックを防ぐための優れた方法ですが、限られたリソースに対する高い競合の問題を解決しません。
Blrfl

79

マルチスレッド簡単です。マルチスレッド用のアプリケーションのコーディングは非常に簡単です。

簡単なトリックがあります。これは、適切に設計されたメッセージキューを使用して(独自にロールしないで)スレッド間でデータを渡すことです。

難しいのは、複数のスレッドが何らかの方法で共有オブジェクトを魔法のように更新しようとすることです。人々は現在の競合状態に注意を払わないため、エラーが発生しやすくなります。

多くの人はメッセージキューを使用せず、共有オブジェクトを更新し、自分で問題を作成しようとします。

困難になるのは、複数のキュー間でデータを渡すときに適切に機能するアルゴリズムを設計することです。それは難しいです。しかし、(共有キューを介して)共存するスレッドの仕組みは簡単です。

また、スレッド I / Oリソースを共有することに注意してください。I / Oにバインドされたプログラム(つまり、ネットワーク接続、ファイル操作、またはデータベース操作)は、多くのスレッドで高速化することはほとんどありません。

共有オブジェクトの更新の問題を説明する場合、それは簡単です。たくさんの紙のカードを持ってテーブルに座ってください。4〜6個の単純な計算式の簡単な計算セットを書き留めてください。

これがゲームです。あなたはそれぞれ式を読み、答えを書き、その答えが書かれたカードを置きます。

各自が半分の作業を行いますよね?半分の時間で完了しましたよね?

上司があまり考えずに開始したばかりの場合は、何らかの形で矛盾が生じ、両方が同じ式に答えを書きます。書く前に読んでいる二人の間に固有の競合状態があるため、それはうまくいきませんでした。同じ式を読んだり、お互いの答えを上書きしたりすることを妨げるものは何もありません。

ひどくロックされていない、またはロックされていないリソースで競合状態を作成する方法はたくさんあります。

すべての競合を回避する場合は、用紙を数式のスタックに分割します。キューから1つを取り出し、回答を書き留めて、回答を投稿します。どちらも読み取り専用のメッセージキューから読み取るため、競合はありません。


紙をスタックに切り分けても、完全に解決するわけではありません-あなたとあなたの上司が同時に新しいフォーミュラに手を伸ばし、指の関節を彼に叩き込むという状況がまだあります。実際、これは最も一般的な種類のスレッド化問題の代表であると言えます。本当に重大なエラーは早期に発見されます。誰もそれらを再現できないため、本当に異常なエラーが永遠に存在し続けます。もっともらしいレース条件-このような-はテスト中に発生し続け、最終的にそれらのすべて(またはより可能性が高い)が解決されます。
Airsource Ltd

@AirsourceLtd「ナックルを彼に叩きつけて」とはどういうことですか?2つの異なるスレッドが同じメッセージを取得できないようにするメッセージキューがあれば、問題はありません。あなたの言っていることを誤解していない限り。
ザック

25

マルチスレッドプログラミングは、おそらく同時実行性の最も難しいソリューションです。基本的には、マシンが実際に行うことの非常に低レベルの抽象化です。

アクターモデル(ソフトウェア)トランザクションメモリなど、はるかに簡単なアプローチがいくつかあります。または、不変のデータ構造(リストやツリーなど)を操作します。

一般的に、懸念事項を適切に分離することにより、マルチスレッド化が容易になります。人々が20個のスレッドを生成し、すべてが同じバッファを処理しようとすると、忘れられがちです。同期が必要な場所でリアクタを使用し、通常はメッセージキューを使用して異なるワーカー間でデータを渡します。
アプリケーションロジックにロックがある場合、何か間違ったことをしました。

そのため、技術的には、マルチスレッドは困難です。
「ロックを平手打ち」は、同時実行性の問題に対するほとんどスケーラブルでないソリューションであり、実際にはマルチスレッドの目的全体を無効にします。それは、問題を非並行実行モデルに戻すことです。実行すればするほど、その時点で実行されているスレッドは1つだけになります(デッドロックの場合は0)。それは全体の目的を無効にします。
これは、「第三世界の問題を解決するのは簡単です。ただ爆弾を投げてください」と言っているようなものです。些細な解決策があるからといって、結果の質を気にするので、これは問題を些細なものにしない。

しかし、実際には、これらの問題を解決することは、他のプログラミングの問題と同様に困難であり、適切な抽象化を行うのが最善です。実際、これは非常に簡単です。


14

この質問には非技術的な角度があると思います-IMOは信頼の問題です。たとえば、Facebookなどの複雑なアプリを再現するように頼まれます。私は、タスクの複雑さを未経験/管理者に説明しなければならない場合、デンマークで何かが腐っているという結論に達しました。

他の忍者プログラマが5分でタスクを実行できたとしても、あなたの推定はあなたの個人的な能力に基づいています。あなたの対談者は、その問題についてあなたの意見を信頼することを学ぶか、彼らが受け入れる言葉を持っている人を雇うべきです。

課題は、人々が会話を通じて無視する傾向があるか、把握できない技術的意味を伝えることではなく、相互尊重の関係を確立することです。


1
興味深い答えですが、それは技術的な質問です。しかし、私はあなたの言うことに同意します...しかし、この場合、私のマネージャーはかなり良いプログラマーですが、マルチスレッドのアプリの複雑さに出会っていないので、彼はそれらを過小評価しています。
氏Shoubs

6

デッドロックを理解するための簡単な思考実験の1つは、「食事の哲学者」問題です。競合状態がどれほど悪いかを説明するために私が使用する傾向がある例の1つは、Therac 25の状況です。

「ロックをたたくだけ」とは、マルチスレッドで難しいバグに遭遇したことのない人の考え方です。そして、はあなたが状況の深刻さを誇張している考えている可能性があります(私はそうではありません-特に車で終わる組み込みソフトウェアでは、競合状態のバグで物を爆破したり、人々を殺すことは可能です)。


1
つまり、サンドイッチの問題です。サンドイッチをたくさん作りますが、バター皿とナイフは1つしかありません。一般的には大丈夫ですが、最終的には誰かがバターをつかみ、他の誰かがナイフをつかむ....
gbjbaanb

このようなデッドロックの問題は、常に一定の順序でリソースを取得することで解決できますか?
コンプマン

@compman、いいえ。なぜなら、2つのスレッドが同じリソースを同時に取得しようとする可能性があり、それらのスレッドは必ずしも同じリソースセットを必要としないからです。問題を引き起こすのに十分なオーバーラップだけです。1つの方法は、リソースを「元に戻し」、ランダムな期間待機してから再びそれを取得することです。このバックオフ期間は多くのプロトコルで発生しますが、最も初期のプロトコルはアロハでした。en.wikipedia.org/wiki/ALOHAnet
タングレナ

1
プログラム内のすべてのリソースに番号があり、スレッド/プロセスがリソースのセットを必要とするとき、それは常に昇順でリソースをロックしますか?デッドロックが起こるとは思わない。
コンプマン

1
@compman:それは確かにデッドロックを回避する方法です。これを自動的に確認できるツールを設計することは可能です。したがって、アプリケーションが数値の昇順以外のリソースをロックすることが検出されない場合、潜在的なデッドロックは発生していません。(潜在的なデッドロックは、コードが顧客のコンピューターで実行されたときにのみ実際のデッドロックに変わることに注意してください)。
gnasher729

3

同時アプリケーションは決定論的ではありません。プログラマが脆弱であると認識している非常に少量のコード全体では、スレッド/プロセスの一部が別のスレッドの一部に関連して実行されるタイミングを制御できません。テストはより難しく、時間がかかり、すべての並行性関連の欠陥を見つける可能性は低いです。欠陥が見つかった場合、微妙であることが多く、一貫して再現できないため、修正が困難です。

したがって、唯一の正しい同時アプリケーションは、おそらく正しいと思われるアプリケーションであり、ソフトウェア開発ではあまり実践されていません。結果として、メッセージの受け渡しは比較的簡単に正しいことを証明できるため、S.Lotによる回答が最良の一般的なアドバイスです。


3

2つの言葉での短い答え:観測可能な非決定性

長い答え:問題を考慮して、使用する並行プログラミングへのアプローチに依存します。書籍Concepts、Techniques、and Models of Computer Programmingで、著者は並行プログラムを書くための4つの主な実用的アプローチを明確に説明しています:

  • 順次プログラミング:並行性のないベースラインアプローチ。
  • 宣言的並行性:観測可能な非決定性がない場合に使用できます。
  • メッセージパッシングの同時実行性:多数のエンティティ間でのメッセージの同時受け渡し。各エンティティは内部的にメッセージを順次処理します。
  • 共有状態の同時実行:スレッドは、粗雑なアトミックアクション(ロック、モニター、トランザクションなど)によって共有パッシブオブジェクトを更新します。

今、明らかにシーケンシャルプログラミングから離れてこれらの4つのアプローチの最も簡単である宣言型同時実行は、このアプローチを使用して書かれたプログラムを持っていないので、観察可能な非決定性を。言い換えると、競合状態は観察可能な非決定的動作であるため、競合状態はありません

しかし、観測可能な非決定性の欠如は、宣言的並行性を使用して取り組むことができないいくつかの問題があることを意味します。ここでは、それほど簡単ではない最後の2つのアプローチが登場します。それほど簡単ではない部分は、観測可能な非決定性の結果です。現在、これらは両方ともステートフルコンカレントモデルに該当し、表現力も同等です。しかし、CPUあたりのコア数は増え続けているため、業界では最近、メッセージパッシングライブラリ(AVM for JVM)やプログラミング言語(Erlangなど)の増加に見られるように、メッセージパッシングの同時実行性に関心が集まっているようです。。

理論的なアクターモデルに支えられた前述のAkkaライブラリは、ロック、モニター、またはトランザクションを処理する必要がないため、同時アプリケーションの構築を簡素化します。一方、ソリューションを設計するための異なるアプローチ、つまり、アクターを階層的に合成する方法で考える必要があります。まったく異なる考え方が必要だと言うことができますが、これもまた、最終的には、単純な状態の共有同時実行を使用するよりもさらに困難になる可能性があります。

観察可能な非決定性のために並行プログラミングは困難ですが、特定の問題に対して適切なアプローチとそのアプローチをサポートする適切なライブラリを使用すると、多くの問題を回避できます。


0

私は最初に、2つのスレッドを開始し、それらを両方とも1から100まで同時にコンソールに出力する単純なプログラムを見ることで問題を引き起こす可能性があると教えられました。の代わりに:

1
1
2
2
3
3
...

次のようなものが得られます。

1
2
1
3
2
3
...

もう一度実行すると、まったく異なる結果が得られる場合があります。

私たちのほとんどは、コードが順番に実行されると想定するように訓練されています。ほとんどのマルチスレッドでは、これを「箱から出して」当然のことと見なすことはできません。


-3

いくつかのハンマーを使用して、ハンマーを保持している人とのやり取りをせずに、狭い間隔の釘を一度につぶしてみてください...(目隠しされていると仮定します)。

これをエスカレートして家を建てます。

あなたが建築家であると想像して、夜眠りましょう。:)


-3

簡単な部分:セマフォ、キュー、インターロックカウンター、アトミックボックス型など、フレームワーク、オペレーティングシステム、ハードウェアの最新機能を備えたマルチスレッドを使用します。

難しい部分:そもそも機能を使用せずに機能を実装します。ハードウェアの非常に限られた機能を除き、複数のコアにわたるクロッキングの一貫性の保証のみに依存する場合があります。


3
難しい部分は確かに難しいですが、その簡単な部分でさえそれほど簡単ではありません。
-PeterAllenWebb
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.