マルチスレッドおよびマルチプロセッサプログラミングの非推奨のプラクティスはありますか?


36

FORTRANとBASICの初期には、本質的にすべてのプログラムはGOTOステートメントで記述されていました。結果はスパゲッティコードであり、ソリューションは構造化プログラミングでした。

同様に、ポインターはプログラムの特性を制御するのが難しい場合があります。C ++は多くのポインターで開始しましたが、参照の使用をお勧めします。STLのようなライブラリは、依存関係の一部を軽減できます。より優れた特性を持つスマートポインターを作成するイディオムもあり、C ++の一部のバージョンでは参照とマネージコードを許可しています。

継承やポリモーフィズムなどのプログラミング手法では、舞台裏で多くのポインターを使用します(ただし、構造化プログラミングでは分岐命令で満たされたコードを生成します)。Javaのような言語は、プログラマーに依存せずにポインターを削除し、ガベージコレクションを使用して動的に割り当てられたデータを管理します。

私の読書では、セマフォを使用していないように見えるマルチプロセスおよびマルチスレッドのプログラミングの例を見てきました。異なる名前で同じものを使用していますか、それとも同時使用からリソースの保護を構築する新しい方法がありますか?

たとえば、マルチコアプロセッサを使用したマルチスレッドプログラミングシステムの具体例は、OpenMPです。環境に含まれていないように見えるセマフォを使用せずに、次のように重要な領域を表します。

th_id = omp_get_thread_num();
#pragma omp critical
{
  cout << "Hello World from thread " << th_id << '\n';
}

この例は、http//en.wikipedia.org/wiki/OpenMPからの抜粋です

あるいは、関数wait()およびsignal()を使用したセマフォを使用したスレッドの相互の保護は、次のようになります。

wait(sem);
th_id = get_thread_num();
cout << "Hello World from thread " << th_id << '\n';
signal(sem);

この例では、物事は非常に単純であり、wait()およびsignal()呼び出しが一致していることを示すには簡単なレビューで十分であり、多くの並行性がある場合でもスレッドセーフが提供されます。しかし、他のアルゴリズムはより複雑で、複数のセマフォ(バイナリとカウントの両方)を使用し、多くのスレッドが呼び出すことができる複雑な条件を持つ複数の関数に分散しています。デッドロックを作成したり、物事をスレッドセーフにできなかったりすることの結果は、管理が難しい場合があります。

OpenMPのようなこれらのシステムはセマフォの問題を排除しますか?
彼らは問題を他のどこかに移動させますか?
セマフォを使用しないようにアルゴリズムを使用してお気に入りのセマフォを変換するにはどうすればよいですか?


あなたは正確に何について話しているのですか?あなたは何を見たの?
svick

4
失礼であることを意味しませんが、最初の3つの段落を削除することもできます。彼らはあなたの質問に実際には関与しておらず、彼らの結論に行き過ぎており、多くの議論を生み出します。
-dbracey

1
おっと、大きな編集。私は答えを突き刺しました。質問は、GOTO、ポインター、継承、およびポリモーフィズムの中をさまよいますが、私の答えでは、これらの問題を脇に置き、「非推奨のプラクティス」の質問に焦点を合わせました。
スチュアートマーク

回答:


15

使用すべきではない並行プログラミングのテクニックとプラクティスはありますか?はいと言います

最近では珍しいと思われる1つの初期の並行プログラミング手法は、割り込み駆動型プログラミングです。これが、1970年代のUNIXの仕組みです。UNIXに関するライオンズの解説またはバッハのUNIXオペレーティングシステム設計を参照してください。簡単に言うと、この手法は、データ構造を操作している間に割り込みを一時的に中断し、その後割り込みを復元することです。BSDのSPL(9)のマニュアルページこのスタイルのコーディングの例があります。割り込みはハードウェア指向であり、コードはハードウェア割り込みの種類とそのハードウェアに関連付けられたデータ構造との間の暗黙的な関係を具体化することに注意してください。たとえば、ディスクI / Oバッファを操作するコードは、それらのバッファを操作している間、ディスクコントローラハードウェアからの割り込みを一時停止する必要があります。

このスタイルのプログラミングは、ユニプロセッサハードウェア上のオペレーティングシステムで採用されていました。アプリケーションが割り込みを処理することは非常にまれでした。一部のOSにはソフトウェア割り込みがあり、人々はそれらの上にスレッドシステムまたはコルーチンシステムを構築しようとしましたが、これはあまり普及していませんでした。(確かにUNIXの世界ではそうではありません。)今日、割り込みスタイルのプログラミングは、小さな組み込みシステムやリアルタイムシステムに限定されていると思います。

セマフォは、ソフトウェア構成(ハードウェアに関連しない)であり、ハードウェア機能の抽象化を提供し、マルチスレッドとマルチプロセッシングを可能にするため、割り込みよりも進歩しています。主な問題は、構造化されていないことです。プログラマは、各セマフォとそれが保護するデータ構造との関係を、プログラム全体でグローバルに維持する責任があります。このため、今日ではベアセマフォはめったに使用されないと思います。

もう1つの小さなステップはmonitorです。これは、保護されているデータを使用して同時実行制御メカニズム(ロックおよび条件)をカプセル化します。これは、Mesa (代替リンク)システムに引き継がれ、そこからJavaに引き継がれました。(このMesaの論文を読むと、Javaのモニターロックと条件がMesaからほぼそのままコピーされていることがわかります。)モニターは、十分な注意と勤勉なプログラマーがコードとデータに関するローカル推論のみを使用して安全に並行プログラムを作成できる点で役立ちますモニター内。

Javaのjava.util.concurrentパッケージにあるような追加のライブラリ構造があります。これには、さまざまな高度に並行したデータ構造とスレッドプーリング構造が含まれます。これらは、スレッドの閉じ込めや効果的な不変性などの追加の技術と組み合わせることができます。Goetz et。による「Java Concurrency In Practice」を参照してください。al。さらなる議論のために。残念ながら、多くのプログラマーはロックと条件を使用して独自のデータ構造をローリングしていますが、ライブラリ作成者がすでに重い作業を行っているConcurrentHashMapのようなものを実際に使用する必要があります。

上記のすべてには、いくつかの重要な特性があります。グローバルに共有された可変状態を介して相互作用する複数の制御スレッドがあります。問題は、このスタイルのプログラミングでは依然としてエラーが発生しやすいことです。小さな間違いが見過ごされることは非常に簡単であり、再現や診断が困難な不正行為につながります。この方法で大規模システムを開発するために「十分に注意して勤勉」なプログラマーはいないかもしれません。少なくとも、ほとんどありません。したがって、共有可能な可変状態を使用したマルチスレッドプログラミングは、可能な限り回避する必要があります。

残念ながら、すべての場合に回避できるかどうかは完全には明らかではありません。まだ多くのプログラミングがこの方法で行われています。これが何か他のものに取って代わられるのは素晴らしいことです。Jarrod Robersondavidk01からの回答は、不変データ、関数型プログラミング、STM、メッセージ受け渡しなどの手法を示しています。それらを推奨するものはたくさんあり、すべてが積極的に開発されています。しかし、私は彼らがまだ古き良き共有可変状態を完全に置き換えたとは思わない。

編集:最後に、特定の質問に対する私の回答を示します。

OpenMPについてあまり知りません。私の印象では、数値シミュレーションなどの高度な並列問題に対して非常に効果的です。しかし、それは汎用的ではないようです。セマフォ構造は非常に低レベルのようであり、プログラマがセマフォと共有データ構造との関係を維持する必要があります。上記のすべての問題があります。

セマフォを使用する並列アルゴリズムを使用している場合、それを変換する一般的な手法は知りません。それをオブジェクトにリファクタリングし、その周りにいくつかの抽象化を構築できるかもしれません。しかし、メッセージの受け渡しなどを使用する場合は、問題全体を再概念化する必要があると思います。


おかげで、これは素晴らしい情報です。私は参考文献を調べ、あなたが言及した私の新しい概念についてより深く掘り下げます。
DeveloperDon

java.util.concurrentに+1を付け、コメントに同意しました-1.5からJDKにあり、使用されることはめったにありません。
MebAlone

1
構造が既に存在する場合、独自の構造を転がさないことがいかに重要かを強調してほしい。非常に多く、非常に多くのバグ...
corsiKa

「セマフォは、ハードウェアに関連しないソフトウェア構成であるため、割り込みよりも進歩している」と言うのは正確ではないと思います。セマフォは、Compare-and-Swap命令を実装するためにCPUに依存するか、マルチコアバリアントです。
ジョシュ・ピアース

もちろんセマフォ@JoshPearceは実装ハードウェア構築物を使用して、彼らは、抽象化などCAS、テスト・アンド・セット、cmpxchng、などの任意の特定のハードウェア構築、無関係である
スチュアートマーク

28

質問への回答

一般的なコンセンサスは、可変状態はBad™であり、不変状態はGood™であり、関数型言語と命令型言語によって何度も正確で真実であることが証明されています。

問題は、主流の命令型言語がこの作業方法を処理するように設計されていないことであり、これらの言語の状況は一晩で変わることはありません。これは、との比較にGOTO欠陥がある場所です。不変の状態とメッセージの受け渡しは素晴らしい解決策ですが、万能薬でもありません。

欠陥のある前提

この質問は、欠陥のある前提との比較に基づいています。これGOTOが実際の問題であり、言語設計者およびソフトウェアエンジニアリング連合インターガラティックユニバーサルボードによって何らかの方法で一般的に廃止されました©!GOTOメカニズムがなければ、ASMはまったく機能しません。生のポインターがCまたはC ++の問題であり、スマートポインターが万能薬であるという前提と同じですが、そうではありません。

GOTO問題ではなく、プログラマーが問題でした。共有された可変状態についても同様です。それ自体は問題ではなく、それを使用しているプログラマーが問題です。競合状態やバグが発生しない方法で共有可能な可変状態を使用するコードを生成する方法があれば、それは問題になりません。スパゲッティコードを作成したり、同等のコンストラクトを作成したりしない場合でも、問題にはなりません。GOTO

教育は万能薬です

馬鹿プログラマーはそうでしたdeprecated、すべての人気のある言語はまだGOTO直接または間接的に構造を持ち、このタイプの構造を持つすべての言語で適切に使用されるbest practiceときです。

例: Javaにはラベルがあり、try/catch/finally両方ともGOTOステートメントとして直接機能します。

私が話すほとんどのJavaプログラマーは、immutable彼らの外で実際に何を意味しているのかさえ知らずthe String class is immutable、彼らの目を見てゾンビを繰り返します。彼らは間違いなくクラスを作成するためにfinalキーワードを適切使用する方法を知りませんimmutable。だから、不変のメッセージ使用したメッセージの受け渡しがなぜ素晴らしいのか、共有された可変状態がそれほど優れていないのか、彼らにはまったく分からないと確信しています。


3
+1すばらしい答え、明確に書かれ、可変状態の基本パターンを特定します。IUBLDSEUはミームになるはずです:)
ディベケ

2
GOTOは「どうか、ここで炎の戦争を始めてはいけません。あえてダブルドッグしてください」のコードワードです。この質問は火を消しますが、本当に良い答えを与えません。関数型プログラミングと不変性に関する名誉ある言及は素晴らしいですが、それらのステートメントには肉はありません。
エヴァンプライス

1
これは矛盾する答えのようです。最初に、「Aは悪い、Bは良い」と言い、次に「馬鹿は非推奨」と言います。同じことが最初の段落にも当てはまりませんか?答えの最後の部分を取り上げて、「すべての言語で適切に使用する場合、共有可能な可変状態はベストプラクティスである」と言うことはできません。また、「証明」は非常に強力な言葉です。本当に強力な証拠がない限り、使用しないでください。
ルイスキューバル

2
炎戦争を始めるつもりはなかった。Jarrodが私のコメントに反応するまで、GOTOは議論の余地がなく、類推でうまく機能すると考えていました。私が質問を書いたとき、それは私には起こりませんでしたが、ダイクストラはGOTOとセマフォの両方でゼロにありました。Edsger Dijkstraは私にとって巨人のように思え、GOTOに関するセマフォ(1965)と初期(1968)の学術研究の功績が認められました。ダイクストラの擁護方法は、しばしば無愛想で対立的でした。論争/対立は彼のために働いたが、私はセマフォの可能な代替案についてのアイデアが欲しいだけです。
DeveloperDon

1
多くのプログラムは、現実の世界では変更可能なものをモデル化することになっています。午前5時37分に、オブジェクト#451がその瞬間(5時37分)に現実世界の何かの状態を保持し、その後に現実世界の事物の状態が変化した場合、オブジェクトのアイデンティティが実世界の物の状態が不変であること(つまり、物は常にオブジェクト#451で表されます)、またはオブジェクト#451が不変であることの両方ではありません。多くの場合、IDを不変にする方が、オブジェクト#451を不変にするよりも役立ちます。
-supercat

27

学界での最新の怒りはソフトウェアトランザクションメモリ(STM)のようで、十分にスマートなコンパイラテクノロジを使用することにより、プログラマの手からマルチスレッドプログラミングのすべての毛深い詳細を取り除くことを約束します。舞台裏ではまだロックとセマフォですが、プログラマーであるあなたはそれについて心配する必要はありません。そのアプローチの利点はまだ明確ではなく、明白な競合者はいません。

Erlangは同時実行のためにメッセージパッシングとエージェントを使用します。これは、STMよりも簡単に動作するモデルです。メッセージの受け渡しでは、各エージェントが独自のミニユニバースで動作するため、データに関連する競合状態がないため、心配するロックやセマフォはまったくありません。あなたはまだいくつかの奇妙なエッジケースを持っていますが、それらはライブロックやデッドロックほど複雑ではありません。JVM言語はAkkaを利用してメッセージパッシングとアクターのすべての利点を得ることができますが、Erlangとは異なり、JVMはアクターの組み込みサポートを持たないため、Akkaは依然としてスレッドとロックを使用していますが、プログラマはそれを心配する必要はありません。

私はロックやスレッドを使用しないことを承知している他のモデルが使用している先物本当に非同期プログラミングのちょうど別の形です。

このテクノロジーがC ++でどれだけ利用できるかはわかりませんが、スレッドとロックを明示的に使用していないものを見ている場合は、上記の並行性を管理する手法の1つになるでしょう。


新しい用語「毛深い詳細」に対して+1。笑 私はこの新しい用語を笑うのをやめることはできません。これからは「毛深いコード」を使うつもりです。
サイードネアマティ

1
@Saeed:以前にその表現を聞いたことがありますが、それほど珍しいことではありません。私はそれが面白いことに同意します:
キャメロン

1
いい答えだ。.NET CLIは(ロックとは対照的に)シグナリングもサポートしていると思われますが、ロックを完全に置き換えた例にはまだ出会っていません。非同期がカウントされるかどうかはわかりません。Javascript / NodeJのようなプラットフォームについて話している場合、それらは実際にはシングルスレッドであり、リソース制限の最大化(つまり、大量の使い捨てコンテキスト)の影響を受けにくいため、IO負荷が高い場合にのみ優れています。CPUに負荷がかかる負荷では、非同期プログラミングを使用してもメリットはほとんどありません。
エヴァン

1
興味深い答えは、私は先物に出くわしたことがありませんでした。また、Erlangのようなメッセージパッシングシステムでデッドロックライブロック保持できることに注意してください。CSPを使用すると、デッドロックライブロックについて正式に推論できますが、それ自体では防止できません。
マークブース

1
Lock freeとwait freeデータ構造をそのリストに追加します。
ストーンメタル

3

これは主に抽象化のレベルに関するものだと思います。プログラミングではかなり頻繁に、いくつかの詳細をより安全な方法またはより読みやすい方法で抽象化することが有用です。

これは、制御構造に適用される:ifS、forSとさえtry- catchブロックはオーバーだけで抽象化しているgotoのは。これらの抽象化は、コードをより読みやすくするため、ほとんどの場合に役立ちます。ただし、使用する必要がある場合がありますgoto(たとえば、アセンブリを手作業で記述している場合)。

これはメモリ管理にも適用されます。C++スマートポインタとGCは、生のポインタと手動でのメモリの割り当て解除/割り当ての抽象化です。そして、時には、これらの抽象化は適切ではありません。例えば、本当に最高のパフォーマンスが必要なときです。

また、マルチスレッドにも同じことが当てはまります。先物やアクターなどは、スレッド、セマフォー、ミューテックス、CAS命令の単なる抽象化です。このような抽象化は、コードをより読みやすくするのに役立ち、エラーを回避するのにも役立ちます。しかし、時々、それらは単に適切ではありません。

使用可能なツールと、その長所と短所を知っておく必要があります。次に、タスクの正しい抽象化を選択できます(存在する場合)。高レベルの抽象化は低レベルを非難しません。抽象化が適切でなく、「古い方法」を使用することが最善の選択である場合が常にあります。


おかげで、あなたは類推をキャッチしている、そして私は答えWRTセマフォがそれらが非推奨であるか否かであるかどうかについて先入観の考えを持っていないし、ひらひらすることすらありません。私にとってより大きな質問は、より良い方法があり、セマフォに重要な何かが欠けていないようで、マルチスレッドアルゴリズムの全範囲を実行できないシステムであるということです。
DeveloperDon

2

はい、しかしあなたはそれらのいくつかに出くわす可能性は低いです。

昔は、適切なミューテックスを作成するのが難しいため、ブロッキングメソッド(バリア同期)を使用するのが一般的でした。最近の並行ライブラリーを使用すると、並列化とプロセス間調整のためのより豊富で徹底的にテストされたツールのセットが提供されるため、このことの痕跡を今でも見ることができます。

同様に、古い慣習は、手作業で並列化する方法を理解できるように、拷問的なコードを書くことでした。この形式の(最適化は間違っている可能性があります)最適化は、これを行うコンパイラーの出現、必要に応じてループの巻き戻し、分岐の予測的な追跡などにより、主に窓から出ています。ただし、これは新しいテクノロジーではありません、少なくとも15年は市場に出回っています。スレッドプールのようなものを利用することは、かつての非常に厄介なコードを回避します。

おそらく、非推奨の方法は、最新の十分にテストされたライブラリを使用する代わりに、並行性コードを自分で書くことです。


ありがとう。並行プログラミングの使用には大きな可能性があるようですが、きちんとした方法で使用しないと、Pandoraの箱になる可能性があります。
DeveloperDon

2

AppleのGrand Central Dispatchは、並行性についての私の考えを変えたエレガントな抽象化です。私の謙虚な経験では、キューに重点を置いているため、非同期ロジックの実装が1桁簡単になります。

利用可能な環境でプログラミングすると、スレッド、ロック、スレッド間通信の使用のほとんどが置き換えられました。


1

並列プログラミングの主な変更点の1つは、CPUが以前よりも非常に高速になりましたが、そのパフォーマンスを実現するには、キャッシュを適切に満たす必要があることです。複数のスレッドを同時に実行しようとすると、ほとんどの場合、スレッドごとにキャッシュが無効になります(つまり、各スレッドは異なるデータを操作する必要があります)。遅いCPUで使用します。

これが、非同期またはタスクベース(Grand Central Dispatch、IntelのTBBなど)のフレームワークが一般的である理由の1つです。一度にコード1タスクを実行し、次のタスクに移動する前にタスクを終了します。設計を台無しにしたくない限り、各タスクはほとんど時間がかかりません(つまり、並列タスクは実際にキューに入れられます)。CPUを集中的に使用するタスクは、すべてのタスクを処理する単一スレッドで処理されるのではなく、代替CPUコアに渡されます。本当にマルチスレッド処理が行われていない場合も管理が容易です。


クール。AppleとIntelの技術に言及してくれてありがとう。あなたの答えは、スレッドとコアの親和性の管理の課題を指摘していますか?マルチコアプロセッサがコアごとにL1キャッシュを繰り返す可能性があるため、いくつかのキャッシュパフォーマンスの問題が緩和されますか?例:software.intel.com/en-us/articles/… キャッシュヒットの多い4つのコアの高速キャッシュは、同じデータでキャッシュミスの多い1つのコアの4倍以上の速度になります。行列乗算ができます。4コアの32スレッドのランダムスケジューリングはできません。アフィニティを使用して、32個のコアを取得しましょう。
DeveloperDon

実際には同じ問題ではありません-コアアフィニティは、タスクがコアからコアにバウンスされる問題を指します。タスクが中断され、新しいタスクに置き換えられた場合、同じ問題が発生し、元のタスクは同じコアで続行されます。Intelは、コアの数に関係なく、キャッシュヒット=高速、キャッシュミス=低速と言っています。私は彼らのチップではなく、のAMDを買うためにあなたを説得しようとしていると思います:)
gbjbaanb
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.