コンピュータサイエンスにおいて、ロックまたはミューテックス(相互排他性から派生)は、複数の実行スレッドによる同時実行状態の変更やアクセスを防ぐ同期プリミティブです。ロックは相互排他性に基づく同時実行制御ポリシーを強制するもので、様々な実装方法があるため、アプリケーションごとに異なる実装が存在します。
一般的に、ロックはアドバイザリロックであり、各スレッドは対応するデータにアクセスする前にロックを取得することで協力します。一部のシステムでは、強制ロックも実装されています。強制ロックでは、ロックされたリソースへの不正アクセスを試みると、アクセスを試みたエンティティで 例外が強制的に発生します。
最も単純なロックはバイナリセマフォです。これはロックされたデータへの排他アクセスを提供します。他の方式では、データの読み取りに共有アクセスも提供されます。その他に広く実装されているアクセスモードには、排他、除外意図、アップグレード意図などがあります。
ロックを分類する別の方法は、ロック戦略によってスレッドの進行が妨げられた場合に何が起こるかによって分類することです。ほとんどのロック設計では、ロックを要求しているスレッドの実行は、ロックされたリソースへのアクセスが許可されるまでブロックされます。スピンロックでは、スレッドはロックが利用可能になるまで単に待機(「スピン」)します。これは、スレッドが短時間ブロックされている場合に効率的です。これは、オペレーティングシステムのプロセス再スケジュールのオーバーヘッドを回避できるためです。ロックが長時間保持される場合、またはロックを保持しているスレッドの進行がロックされたスレッドのプリエンプションに依存する場合は非効率的です。
ロックを効率的に実装するには、通常、ハードウェアによるサポートが必要です。このサポートは通常、 「テスト・アンド・セット」、「フェッチ・アンド・アッド」、「コンペア・アンド・スワップ」といった1つ以上のアトミック命令の形で行われます。これらの命令により、単一のプロセスでロックが解放されているかどうかをテストし、解放されている場合は単一のアトミック操作でロックを取得できます。
ユニプロセッサアーキテクチャでは、割り込み不可能な命令シーケンス(特殊な命令または命令プレフィックスを使用して一時的に割り込みを無効にする)を使用するオプションがありますが、この手法はマルチプロセッサ共有メモリマシンでは機能しません。マルチプロセッサ環境でロックを適切にサポートするには、非常に複雑なハードウェアまたはソフトウェアのサポートが必要になり、同期に関する大きな問題が生じる可能性があります。
アトミック操作が必要な理由は、複数のタスクが同じロジックを実行する同時実行性を実現するためです。例えば、次のCコードを考えてみましょう。
if ( lock == 0 ) { // ロックが解除されたら設定しますlock = myPID ; }
上記の例では、複数のタスクが同時にロックをテストする可能性があるため、タスクがロックを保持していることが保証されません。両方のタスクはロックが空いていることを検知するため、もう一方のタスクもロックを設定しようとしていることを認識せずに、ロックの設定を試みます。アトミックロック操作が利用できない場合は、 DekkerアルゴリズムまたはPetersonアルゴリズムを代替として使用できます。
ロックを不注意に使用すると、デッドロックやライブロックが発生する可能性があります。設計時と実行時の両方で、デッドロックやライブロックを回避または回復するための戦略は数多くあります。(最も一般的な戦略は、ロック取得シーケンスを標準化し、相互に依存するロックの組み合わせが常に明確に定義された「カスケード」順序で取得されるようにすることです。)
一部の言語では構文的にロックをサポートしています。C #の例を以下に示します。
public class Account // これはアカウントのモニターです{ // C# 13 より前のバージョンでは `object` を使用しますprivate readonly Lock _balanceLock = new (); private decimal _balance = 0 ;
public void Deposit ( decimal amount ) { // 一度に 1 つのスレッドだけがこのステートメントを実行できます。lock ( _balanceLock ) { _balance += amount ; } }
public void Withdraw ( decimal amount ) { // 一度に 1 つのスレッドだけがこのステートメントを実行できます。lock ( _balanceLock ) { _balance -= amount ; } } }
C# では、.NET 9上の C# 13 でSystem.Threading.Lockが導入されました。
lock(this)インスタンスがパブリックにアクセスできる場合、コードによって問題が発生する可能性があります。 [1]
Javaと同様に、C#でもMethodImplOptions.Synchronized属性を使ってメソッド全体を同期することができます。[2] [3]
[MethodImpl(MethodImplOptions.Synchronized)]
public void SomeMethod () { // 何かを行う}
ロックの粒度について説明する前に、ロックに関する 3 つの概念を理解する必要があります。
同期におけるロックの数を選択する場合、ロックのオーバーヘッドを減らすこととロックの競合を減らすことの間にはトレードオフがあります。
ロックの重要な特性の一つは、その粒度です。粒度とは、ロックが保護するデータの量の尺度です。一般的に、粗い粒度(少数のロックでそれぞれが大きなデータセグメントを保護する)を選択すると、保護されたデータに単一プロセスがアクセスする際のロックオーバーヘッドは減少しますが、複数のプロセスが同時に実行されている場合のパフォーマンスは低下します。これは、ロック競合の増加によるものです。ロックが粗いほど、無関係なプロセスの処理がロックによって停止される可能性が高くなります。逆に、細かい粒度(多数のロックでそれぞれがかなり少量のデータを保護する)を使用すると、ロック自体のオーバーヘッドは増加しますが、ロック競合は減少します。各プロセスが共通のロックセットから複数のロックを保持しなければならない粒度の高いロックは、微妙なロック依存関係を生み出す可能性があります。この微妙な依存関係により、プログラマが気付かないうちにデッドロックを導入する可能性が高くなります。[要出典]
例えば、データベース管理システムでは、ロックは、粒度の高い順に、フィールドの一部、フィールド、レコード、データページ、またはテーブル全体を保護できます。テーブルロックなどの粗い粒度は、単一ユーザーに対して最高のパフォーマンスが得られる傾向がありますが、レコードロックなどの細かい粒度は、複数ユーザーに対して最高のパフォーマンスが得られる傾向があります。
データベースロックは、トランザクションの同期性を保証する手段として使用できます。例えば、トランザクション処理を並行処理する場合(トランザクションをインターリーブする場合)、2フェーズロックを使用することで、トランザクションの同時実行がトランザクションの連続実行と同等になることが保証されます。しかし、データベースにおけるロックの副作用として、デッドロックが発生します。デッドロックは、トランザクション間のロック順序を事前に決定することで防止するか、待機グラフを使用して検出します。デッドロックを回避しながらデータベースの同期性を確保するためのロックの代替手段として、完全に順序付けされたグローバルタイムスタンプを使用する方法があります。
データベース上で複数の同時ユーザーによる操作を管理するメカニズムが採用されています。その目的は、更新内容の消失やダーティリードを防ぐことです。ロックには、悲観的ロックと楽観的ロックの2種類があります。
これらの主要なロックタイプには、それぞれ異なるブロッキング動作を持つ複数のバリエーションと改良版があります。あるロックが別のロックをブロックする場合、2つのロックは「非互換」と呼ばれます。そうでない場合は、2つのロックは「互換」です。多くの場合、技術文献ではロックタイプのブロッキング相互作用は「ロック互換性表」で示されています。以下は、一般的な主要なロックタイプの例です。
コメント:一部の出版物では、表の項目は単に「適合する」または「適合しない」、あるいはそれぞれ「はい」または「いいえ」とマークされている。[5]
ロックベースのリソース保護とスレッド/プロセス同期には多くの欠点があります。
いくつかの同時実行制御戦略は、これらの問題の一部またはすべてを回避します。例えば、ファネルやトークンのシリアル化は、最大の問題であるデッドロックを回避できます。ロックの代替手段としては、ロックフリープログラミング手法やトランザクショナルメモリといった非ブロッキング同期手法があります。しかし、これらの代替手法では、実際のロックメカニズムをオペレーティングシステムのより基本的なレベルで実装する必要があることがよくあります。そのため、アプリケーションレベルではロック実装の詳細作業から解放されるだけで、前述の問題は依然としてアプリケーションの下位レベルで対処する必要があります。
ほとんどの場合、適切なロックはCPUがアトミック命令ストリーム同期手段を提供しているかどうかに依存します(例えば、パイプラインにアイテムを追加または削除する場合、特定のアイテムを追加または削除するために必要なメモリ内容の操作中は、パイプ内の他のアイテムを追加または削除する必要があるすべての同時操作を一時停止する必要があります)。したがって、アプリケーションは、オペレーティングシステムにかかる負担を認識し、不可能な要求の報告を適切に認識できる場合、より堅牢になることが多いです。[要出典]
ロックベースプログラミングの最大の問題の一つは、「ロックは合成できない」ということです。つまり、小さくて正しいロックベースモジュールを、モジュール自体を変更することなく、あるいは少なくともその内部構造を知らずに、同様に正しいより大きなプログラムに組み合わせることは困難です。サイモン・ペイトン・ジョーンズ(ソフトウェアトランザクショナルメモリの提唱者)は、次のような銀行アプリケーションの例を挙げています。[6]複数のクライアントが同時に口座に入金または出金できるAccountクラスを設計し、ある口座から別の口座に送金するためのアルゴリズムを提供します。
問題の最初の部分に対するロックベースの解決策は次のとおりです。
クラスAccount:
メンバーbalance: Integer
メンバーmutex: Lock
メソッドdeposit(n: Integer)
ミューテックス.ロック()
バランス ← バランス + n
ミューテックス.ロック解除()
メソッドwithdraw(n: Integer)
預金(−n)
問題の2番目の部分ははるかに複雑です。逐次プログラムに適した転送ルーチンは次のようになります。
関数transfer(from: アカウント、to: アカウント、amount: 整数)
from.withdraw(金額)
to.deposit(金額)
並行プログラムでは、このアルゴリズムは正しくありません。なぜなら、あるスレッドが転送処理の途中にいる間に、別のスレッドは最初の口座から金額が引き落とされたが、もう一方の口座にはまだ入金されていない状態を観測する可能性があるからです。つまり、お金がシステムから消えてしまうのです。この問題を完全に解決するには、どちらかの口座を変更する前に両方の口座にロックをかけるしかありませんが、その場合、デッドロックを防ぐために、ロックは何らかの任意のグローバル順序に従って設定する必要があります。
function transfer(from: Account, to: Account, amount: Integer)
if from < to // ロックの順序は任意
from.lock()
to.lock()
それ以外
to.lock()
from.lock()
from.withdraw(金額)
to.deposit(金額)
from.unlock()
to.unlock()
このソリューションは、関係するロックの数が増えると複雑になり、転送関数はすべてのロックについて知る必要があるため、それらを非表示にすることはできません。
プログラミング言語によって同期のサポートは異なります。
synchronizelockスレッドにキーワードを提供して、リソースへの排他的アクセスを保証します。SyncLockは、C# のキーワードのようなキーワードを提供しますlock。synchronizedコードブロック、メソッド、またはオブジェクトをロックするためのキーワード[11]と、並行処理安全なデータ構造を備えたライブラリを提供しています。@synchronized[12]を提供し、また、ロックのためのNSLockingプロトコル[16]とともに、NSLock、[13]、NSRecursiveLock、[14]、NSConditionLock [15]などのクラスも提供しています。Mutex内のクラス[18]pthreadsを提供します。Lock threadinglock_type組み込みモジュールiso_fortran_envとlock/文で派生型が提供されています。[20]unlockMutex<T>[22]構造体を提供する。[23]LOCK。MVarこの構造は空であるか、値(通常はリソースへの参照)を含むことができます。リソースを使用しようとするスレッドは、 の値を「取得」しMVar、空のままにしておき、使用が終わったら に戻します。空の からリソースを取得しようとすると、MVarスレッドはリソースが利用可能になるまでブロックされます。[24]ロックの代替として、ソフトウェアトランザクショナルメモリの実装も存在します。[25]ミューテックスは、バイナリセマフォと同じ基本実装を使用することもあるロック機構です。しかし、使用方法は異なります。バイナリセマフォは俗にミューテックスと呼ばれることもありますが、真のミューテックスはより具体的な使用例と定義を持ち、ミューテックスをロックしたタスクのみがロックを解除できるようになっています。この制約は、セマフォの使用に伴う潜在的な問題に対処することを目的としています。
保護オブジェクトは、保護サブプログラムまたは保護エントリなどの可視の保護操作の呼び出しを通じて、共有データへの協調アクセスを提供します。
{{cite book}}: CS1 maint: 数値名: 著者リスト (リンク)
{{cite book}}: CS1 maint: 数値名: 著者リスト (リンク)