現在の「ロックフリー」実装は、ほとんどの場合同じパターンに従います。
- *いくつかの状態を読み、そのコピーを作成します**
- *コピーを変更**
- 連動操作を行う
- 失敗した場合は再試行してください
(*オプション:データ構造/アルゴリズムによって異なります)
最後のビットは不気味にスピンロックに似ています。実際、それは基本的なスピンロックです。:)
これについては@nobugzに同意します。ロックフリーマルチスレッドで使用されるインターロック操作のコストは、実行する必要のあるキャッシュタスクとメモリコヒーレンシータスクによって左右されます。
ただし、「ロックフリー」のデータ構造で得られるのは、「ロック」が非常にきめ細かいことです。これにより、2つの同時スレッドが同じ「ロック」(メモリ位置)にアクセスする可能性が低くなります。
ほとんどの場合の秘訣は、専用のロックがないことです。代わりに、たとえば、配列内のすべての要素またはリンクリスト内のすべてのノードを「スピンロック」として扱います。前回の読み取り以降に更新がなかった場合は、読み取り、変更を行い、更新を試みます。あった場合は、再試行します。
これにより、追加のメモリやリソースの要件を導入することなく、「ロック」(ああ、申し訳ありませんが、非ロック:)が非常に細かくなります。
きめ細かくすることで、待機の可能性が低くなります。追加のリソース要件を導入せずに、可能な限りきめ細かくすることは素晴らしいことだと思いませんか?
ただし、楽しみのほとんどは、正しいロード/ストアの順序を確認することから得られます。
直感に反して、CPUはメモリの読み取り/書き込みを自由に並べ替えることができます。ちなみに、CPUは非常にスマートです。単一のスレッドからこれを観察するのは困難です。ただし、複数のコアでマルチスレッドを実行し始めると、問題が発生します。あなたの直感は崩壊します:命令があなたのコードの前にあるからといって、それが実際にもっと早く起こるという意味ではありません。CPUは命令を順不同で処理できます。特に、メモリアクセスのある命令に対してこれを実行して、メインメモリのレイテンシを隠し、キャッシュをより有効に活用することを好みます。
さて、直感に反して、コードのシーケンスが「トップダウン」で流れるのではなく、シーケンスがまったくないかのように実行され、「悪魔の遊び場」と呼ばれることは間違いありません。どのようなロード/ストアの再注文が行われるかについて正確な答えを出すことは不可能だと思います。その代わり、1は常にの面で話すメイズとmightsと缶と最悪の事態に準備します。「ああ、CPUはこの読み取りをその書き込みの前に来るように並べ替える可能性があるので、この場所にメモリバリアを配置するのが最善です。」
事項であってもこれらの事実によって複雑にされメイズとmightsは、 CPUアーキテクチャ間で異なる場合があります。それは可能性がある、例えば、その何か場合も起こらないことが保証1つのアーキテクチャで 起こるかもしれない他の上。
「ロックフリー」のマルチスレッドを正しく行うには、メモリモデルを理解する必要があります。
ただし、このストーリーでMFENCE
示されているように、メモリモデルと保証を正しく取得することは簡単ではありません。これにより、IntelとAMDは、JVM開発者の間で混乱を引き起こすというドキュメントにいくつかの修正を加えました。結局のところ、開発者が最初から信頼していたドキュメントは、そもそもそれほど正確ではありませんでした。
.NETのロックは暗黙のメモリバリアをもたらすため、それらを安全に使用できます(ほとんどの場合、つまり...たとえば、このJoe Duffy-Brad Abrams-Vance Morrisonの怠惰な初期化、ロック、揮発性物質、およびメモリの素晴らしさを参照してください)障壁。:)(必ずそのページのリンクをたどってください。)
追加のボーナスとして、サイドクエストで.NETメモリモデルを紹介します。:)
VanceMorrisonの「oldiebutgoldie」もあります:すべての開発者がマルチスレッドアプリについて知っておくべきこと。
...そしてもちろん、@Ericが述べたように、ジョーダフィーは、件名に決定的な読み取りです。
優れたSTMは、きめ細かいロックにできるだけ近づけることができ、おそらく手作りの実装に近いか同等のパフォーマンスを提供します。そのうちの一つがあるSTM.NETからDevLabsプロジェクトMSの。
あなたが.NETだけの熱狂者でないなら、ダグ・リーはJSR-166でいくつかの素晴らしい仕事をしました。
Cliff Clickは、Javaと.NETの同時ハッシュテーブルのように、ロックストライピングに依存しないハッシュテーブルについて興味深い見解を持っており、750CPUまで十分に拡張できるようです。
Linuxの領域に足を踏み入れることを恐れない場合は、次の記事で、現在のメモリアーキテクチャの内部と、キャッシュラインの共有によってパフォーマンスが低下する可能性について詳しく説明します。すべてのプログラマがメモリについて知っておくべきこと。
@BenはMPIについて多くのコメントをしました:私はMPIがいくつかの分野で輝くかもしれないことに心から同意します。MPIベースのソリューションは、賢くしようとする中途半端なロックの実装よりも、推論が簡単で、実装が簡単で、エラーが発生しにくい可能性があります。(ただし、主観的には、STMベースのソリューションにも当てはまります。)多くの成功例が示唆しているように、Erlangなどで適切な分散アプリケーションを正しく作成する方が光年簡単であることも間違いありません。
ただし、MPIは、単一のマルチコアシステムで実行されている場合、独自のコストと独自の問題があります。たとえばErlangでは、プロセスのスケジューリングとメッセージキューの同期に関して解決すべき問題があります。
また、MPIシステムは通常、そのコアで、「軽量プロセス」のための一種の協調N:Mスケジューリングを実装します。これは、たとえば、軽量プロセス間に避けられないコンテキストスイッチがあることを意味します。これは「古典的なコンテキストスイッチ」ではなく、ほとんどの場合ユーザースペース操作であり、高速化できることは事実です。ただし、インターロック操作にかかる20〜200サイクル未満にできるかどうかは疑わしいです。ユーザーモードのコンテキスト切り替えは確かに遅いIntelMcRTライブラリでも。軽量プロセスによるN:Mスケジューリングは新しいものではありません。LWPはSolarisに長い間存在していました。彼らは放棄されました。NTには繊維がありました。それらは現在ほとんどが遺物です。NetBSDには「アクティベーション」がありました。彼らは放棄されました。Linuxは、N:Mスレッド化に関して独自の見解を持っていました。今ではやや死んでいるようです。
時折、
新しい候補があります。たとえば、IntelのMcRT、または最近ではMicrosoftのConCRTと一緒のユーザーモードスケジューリングです。
最も低いレベルでは、N:MMPIスケジューラーが実行することを実行します。Erlang(または任意のMPIシステム)は、新しいUMSを活用することで、SMPシステムに大きなメリットをもたらす可能性があります。
OPの質問は、ソリューションのメリットと主観的な議論に関するものではないと思いますが、それに答える必要がある場合は、タスクに依存すると思います。低レベルで高性能の基本データ構造を構築するために、単一のシステムで多くのコアがローロック/「ロックフリー」技術またはSTMのいずれかで、パフォーマンスの点で最良の結果をもたらし、上記のしわが解消されたとしても、パフォーマンスの面でいつでもMPIソリューションを打ち負かすでしょう。例:Erlang。
単一のシステムで実行される適度に複雑なものを構築する場合は、古典的な粗粒度のロックを選択するか、パフォーマンスが非常に重要な場合はSTMを選択します。
分散システムを構築するために、MPIシステムはおそらく自然な選択をするでしょう。(彼らはアクティブではないように見えるが)。
.NET用のMPI実装
もあることに注意してください