ロック_(情報工学)
[Wikipedia|▼Menu]

計算機科学におけるロック (: lock) とは、計算機システム内に複数の動作主体(プロセススレッドなど)のある環境で、データやデバイスなどの計算資源(リソース)へのアクセス制限を課す同期機構。ロックは並行性制御ポリシーを実施する手法のひとつである。アクセス制限を課す動作を「ロックする」、「ロックを取得する」などと表現する。また対義語として、制限を解除することをアンロック(: unlock)という(ロック解放、ロック解除とも)。
概要

ロックは、複数の動作主体が同一のリソースに対して状態を変更する動作を行うとき、不整合な状態が起こらないように制御する並行性制御において用いられる手法の一つである。ある動作主体がロックしたリソースへは、基本的には他の主体による利用は妨げられる。実際には、完全に利用をさせないロックは性能低下が著しいため、複数の主体が取得可能なロックや、他者の読み出しのみ許可するなど、複数のモード(レベル)のロックを用意し必要に応じて使い分ける。

一言でロックといった場合でも、他主体の動作を妨げる基本的機能だけを指す場合、妨げられた主体の待ち動作を含む場合とさまざまである。

動作主体としてプロセスの場合、プロセス間ロック機構がOSによって提供される。スレッドであれば、スレッドライブラリやスレッドを制御するスレッド機構により提供される。動作主体がトランザクションであれば、トランザクションモニターにより提供される。あるいは、リソースがファイルであればファイルロックとなる。
スレッドにおけるロック
種類

スレッドの場合、関連付けられたデータにアクセスする前にロックを獲得するよう各スレッドが協力するものであって、ロックに強制力はない(これを助言ロック; advisory lock と呼ぶ)。システムによっては強制ロック (mandatory lock) を実装しており、ロックされたリソースに許可されていないアクセスを行おうとしたときに例外が発生するようになっている。

(2値の)セマフォは最も単純なロックである。共有モード(リードオンリー)か排他モード(リードライト)かは区別されない。他の方法では、複数のスレッドがリードオンリーアクセスのためにロックを共有する共有モードを用意している。共有 (shared)、排他 (exclusive)、削除目的 (intend-to-exclude)、更新目的 (intend-to-upgrade) などのモードが広く実装されている。

以上のようなロックの種類とは別に、ロックによってスレッドの進行が妨げられたときの動作によっても分類できる。多くのロックは、ロックされたリソースへのアクセスが可能となるまで、プロセスの実行をブロックする。スピンロックは単純にロックを獲得できるまでスレッドが待つ(スピンする)。これは待つ期間が短い場合は効果的で、オペレーティングシステムによるプロセスのスケジューリングオーバヘッドもかからない。ただし、長時間ロックされたままの場合は非常に無駄である。
実装

スレッドにおいてロックはメモリ上の値を用いて実装される。複数のスレッドが同時に値を取得・変更できない必要があるが、これを効率的に実装するため、ロックは一般にハードウェアによるサポートを必要とする。通常、1つ以上のアトミック命令を使用する(「テスト・アンド・セット」、「フェッチ・アンド・アッド」、「コンペア・アンド・スワップ」など)。これらの命令は、プロセスがロックが獲得されていない(フリーである)ことをチェックするとともに、不可分な(アトミックな)処理としてロックを獲得できる。

プロセッサが1つであれば、割り込みを禁止して命令列を実行することで同等の効果が得られる。しかし、マルチプロセッサではこれでは不十分である。マルチプロセッサ環境でロックをサポートすることはハードウェアとソフトウェアの複雑なサポートを要し、同期の主要な課題でもある。

アトミックな操作が必要とされる理由は、並列処理では複数のタスクが同じロジックを同時に実行している可能性があるからである。例えば、以下のC言語のコードを考えてみよう。if (lock == 0) { /* ロックがフリーなのでセットする */ lock = myPID; }

複数のタスクが並行して動作可能なら、上記のコードはロックを獲得できるとは言えない。どちらのタスクもロックがフリーであると判断し、どちらもロックに自身のIDをセットして獲得しようとするが、他のタスクも同時にロックを獲得しようとしていることを知らない。アトミックなロック操作ができないときは、デッカーのアルゴリズムピーターソンのアルゴリズムで代替することもできる。

ロックの不注意な使用によりデッドロックが生じることがある。これはプロセスがあるロックを獲得した状態で別のロックを獲得しようとしたときに発生する。もし2番目のロックが別のプロセスに獲得されていれば、最初のプロセスはブロックされる。2番目のプロセスが最初のプロセスが獲得済みのロックを獲得しようとすると、システムは「デッドロック」状態となり、どちらのプロセスもブロックされたままとなる。これを防ぐための(あるいは回復するための)様々な戦略が設計時や実行時に採用されている。例えば、各ロックが保護するデータの種類によってロックに優先順位を設定し、順位通りでないとロックを獲得できないようにするなどの対処がある(ただし、同じ種類のデータを2つロックしなければならないときなどはデッドロックに注意しなければならない)。

言語によっては構文規則上ロックをサポートしていることもある。C#での例を以下に示す。/// <summary>/// 口座のモニター。/// </summary>class Account { long val = 0; readonly object thisLock = new object(); public void Deposit(long x) { // 一度に1スレッドしか、このブロックを実行できない。 lock (thisLock) { val += x; } } public void Withdraw(long x) { // 一度に1スレッドしか、このブロックを実行できない。 lock (thisLock) { val -= x; } }}

lock (this) は、そのインスタンスにパブリックにアクセスできる場合、問題を生じる[1]

Javaと同様、C#もメソッド全体を同期させることができ、MethodImplOptions.Synchronizedという属性を使用する[2][3]。[MethodImpl(MethodImplOptions.Synchronized)]public void SomeMethod() { // 何かを実行する。}
粒度

ロックの粒度 (Granularity) を説明する前に、ロックに関する3つのコンセプトを説明する。
ロックのオーバヘッド
ロックそのものに要するメモリ領域などの追加のリソース、ロックの初期化などにかかる
CPU時間、ロック操作にかかるCPU時間など。プログラムがロックを多数使えば使うほど、オーバヘッドも大きくなる。
ロックの競合
他のプロセスやスレッドが獲得しているロックを獲得しようとすることをロックの競合という。ロックを細分化すれば、プロセス/スレッド間で競合が発生する可能性は小さくなる。例えば、配列全体ではなく行単位、あるいは要素単位にロックするといったことである。
デッドロック
上述の問題。何らかの処置をしないと複数のタスクがずっと待ち続けることになる。

従って、同期のためのロックの数を決定する際に、ロックのオーバヘッドとロックの競合がトレードオフの関係にある。

ロックの重要な特性として粒度 (Granularity) がある。粒度とは、ロックが保護するデータの大きさである。一般に粗い粒度(ロック数が少なく、各ロックが大きなデータ領域を保護する)ではロックのオーバヘッドは小さいが、複数プロセスが並行動作するときの性能は低下する。これは粗い粒度ではロックの競合が発生し易いためで、ロックによってプロセスがブロックされる確率が非常に高くなる。反対に細かい粒度(ロック数が多く、各ロックが非常に小さなデータを保護する)ではロック自体のオーバヘッドは増大するが、ロックの競合は低減する。また、ロック数を増やすとデッドロックの危険性が増す。

データベース管理システムでは、ロックは粒度によって、レコード単位、データページ単位、テーブル全体などを保護対象とする。テーブル全体などの粗い粒度のロックはシングルユーザーで性能向上させるのに有利であり、レコード単位などの細かい粒度のロックは複数ユーザーでの性能向上に有利である。
データベースのロック

データベースでは、ロックはトランザクションの同時性を保証する手段として使うことが出来る。すなわち、トランザクション処理が並行して行われるとき(インタリービング式トランザクション)、ツーフェーズロックを使ってトランザクションの並行実行が直列化されたトランザクションと等価であることを保証する。しかし、データベース内のロックの副作用としてデッドロックが発生することがある。デッドロックはロック順序を事前に定義しておくことで防いだり、待ち状態グラフ(英語版)を使って検出したりする。データベースの一貫性のためにロックを使う以外の手段として、完全に順序が決定されるグローバルなタイムスタンプを使用することでデッドロックを防ぐこともある。

データベース上の複数のユーザーの同時並行的要求に対応するための機構があり、更新をしそこなったり不正な情報を読み取らせることを防ぐことを目的としている。この場合のロックは悲観的ロックと楽観的ロックに分けられる。
悲観的ロック (Pessimistic locking)
あるユーザーがあるレコードを読み、それを更新しようとして、そのレコードに排他ロックを置き、他のユーザーがそのレコードを操作することを防ぐ。すなわち、他のユーザーはそのロックが解放されるまで当該レコードを操作することができない。この方式には排他が長時間にわたるという欠点があり、システム全体の応答性が悪くなる。データの競合が激しい環境で主に使用する。ロックによってデータを保護することによるコストが、衝突が起きてトランザクションをロールバックするコストより低い場合である。ロックをかけている時間は短いほどよい。
楽観的ロック (Optimistic locking)
複数のユーザーが同時にデータベースにアクセスしたとき、各ユーザーは最初に読み取ったレコード内容のコピーを保持する。あるユーザーがそのレコードを更新しようとしたとき、そのレコードを読み取ってから更新しようとするまでの間に他のユーザーがそのレコードを更新しなかったかどうかを調べる。もし他者によって更新されていたら、今回の更新要求は無視されエラーが返され、更新のやり直しを促される。ロックの必要な区間が短いので、データベースの性能が向上する。更新することが少ないデータベースでは効率的である。更新が同時に要求されることが多いと頻繁に更新が失敗するという欠点がある。データの競合が少ない環境に適している。.NET では、長期間ロックを保持するのが現実的でないモバイルアプリケーションなどでこの戦略をよく使っている[4]


次ページ
記事の検索
おまかせリスト
▼オプションを表示
ブックマーク登録
mixiチェック!
Twitterに投稿
オプション/リンク一覧
話題のニュース
列車運行情報
暇つぶしWikipedia

Size:27 KB
出典: フリー百科事典『ウィキペディア(Wikipedia)
担当:undef