次のエラーが発生したとき、lock
Boolean
変数を使おうとしていました。
'bool'は、lockステートメントで必要な参照タイプではありません
lock
ステートメントでは参照型のみが許可されているようですが、その理由がわかりません。
アンドレアスは彼の コメント で述べています:
[値の型]オブジェクトが1つのスレッドから別のスレッドに渡されると、コピーが作成されるため、スレッドは最終的に2つの異なるオブジェクトで機能し、安全です。
本当ですか?それは、私が次のことをするとき、実際にはx
およびxToTrue
メソッドの2つの異なるxToFalse
を変更しているということですか?
public static class Program {
public static Boolean x = false;
[STAThread]
static void Main(string[] args) {
var t = new Thread(() => xToTrue());
t.Start();
// ...
xToFalse();
}
private static void xToTrue() {
Program.x = true;
}
private static void xToFalse() {
Program.x = false;
}
}
(このコードだけでは明らかにその状態では役に立たず、例にすぎません)
PS:私はこの質問について知っています 値タイプを適切にロックする方法 。私の質問はhowではなく、whyに関連しています。
ここでワイルドな推測を...
しかし、コンパイラーが値型をロックできるようにすると、何もロックされなくなります...値型をlock
に渡すたびに、ボックス化されたコピーを渡します。別の箱入りのコピー。つまり、ロックはまるでまったく異なるオブジェクトであるかのようになります。 (実際にはそうです)
タイプobject
のパラメーターに値タイプを渡すと、参照タイプにボックス化(ラップ)されることに注意してください。これにより、これが発生するたびに新しいオブジェクトになります。
sync root
レコードがないため、値タイプをロックできません。
ロックは、一度に1つのスレッドのみがアクセスできるレコードを持つオブジェクト(同期ブロックルート)に依存するCLRおよびOS内部メカニズムによって実行されます。参照タイプには次のものがあります。
次のように展開されます。
System.Threading.Monitor.Enter(x);
try {
...
}
finally {
System.Threading.Monitor.Exit(x);
}
コンパイルされますが、Monitor.Enter
/Exit
には参照タイプが必要です。値タイプは毎回異なるオブジェクトインスタンスにボックス化されるため、Enter
とExit
への各呼び出しは異なるオブジェクトで動作するためです。
MSDN メソッドを入力 ページから:
Monitorを使用して、値型ではなくオブジェクト(つまり、参照型)をロックします。値型変数をEnterに渡すと、オブジェクトとしてボックス化されます。同じ変数をもう一度Enterに渡すと、別のオブジェクトとしてボックス化され、スレッドはブロックされません。この場合、Monitorが保護していると思われるコードは保護されません。さらに、変数をExitに渡すと、さらに別のオブジェクトが作成されます。 Exitに渡されるオブジェクトはEnterに渡されるオブジェクトとは異なるため、MonitorはSynchronizationLockExceptionをスローします。詳細については、概念トピック「モニター」を参照してください。
.NETチームが開発者を制限し、Monitorが参照のみを操作できるようにしたのはなぜだろうと思いました。まず、ロックのためだけに専用のオブジェクト変数を定義する代わりに、System.Int32
に対してロックするのが良いと思います。これらのロッカーは通常、他に何もしません。
しかし、その言語によって提供される機能は、開発者に役立つだけでなく、強力なセマンティクスを備えている必要があるようです。したがって、値タイプのセマンティクスは、値タイプがコードに現れるときはいつでも、その式が値に評価されるということです。したがって、意味論的な観点から、「lock(x)」と書いて、xがプリミティブ値型である場合、「クリティカルコードのブロックを変数xの値に対してロックする」と言うのと同じです。奇妙なことより、確かに:)。一方、コード内でref変数に遭遇すると、「ああ、それはオブジェクトへの参照です」と考えて、参照がコードブロック、メソッド、クラス、さらにはスレッドとプロセスの間で共有できるため、ガード。
言い換えると、値型変数は、すべての式の実際の値に対して評価されるコードにのみ表示されます-それ以上はありません。
それがポイントの一つだと思います。
これが許可されない理由を概念的に質問している場合、私は答えが値型identityはvalueとまったく同じです(それが値型になります)。
宇宙のどこかでint
4
は同じことについて話している-それをロックするための排他的アクセスをどのように主張できるでしょうか?
値型には、lockステートメントがオブジェクトのロックに使用する同期ブロックがないためです。参照タイプのみがタイプ情報、同期ブロックなどのオーバーヘッドを運びます。
参照型をボックス化すると、値型を含むオブジェクトができ、そのオブジェクトをロックできます(期待します)。オブジェクトに追加のオーバーヘッド(ロックに使用される同期ブロックへのポインター、タイプ情報へのポインタなど)。他の誰もが述べているように-オブジェクトをボックス化すると、ボックス化するたびに新しいオブジェクトが取得され、毎回異なるオブジェクトをロックすることになります-これは、ロックを取る目的を完全に無効にします。
これはおそらく機能します(ただし、それは完全に無意味であり、私は試していません)
int x = 7;
object boxed = (object)x;
//thread1:
lock (boxed){
...
}
//thread2:
lock(boxed){
...
}
誰もがボックス化を使用していて、ボックス化オブジェクトが一度設定されている限り、ボックス化オブジェクトをロックしていて、一度しか作成されていないため、おそらく正しいロックが取得されます。ただし、これを行わないでください。これは単なる思考の練習です(そして、うまくいかない可能性もあります-私が言ったように、私はそれをテストしていません)。
2番目の質問について-いいえ、値はスレッドごとにコピーされません。両方のスレッドは同じブール値を使用しますが、スレッドがその最新の値を表示することは保証されていません(1つのスレッドが値を設定すると、すぐにメモリロケーションに書き戻されない可能性があるため、値を読み取る他のスレッドは「古い」結果)。
以下はMSDNから取得したものです。
Lock(C#)およびSyncLock(Visual Basic)ステートメントを使用して、コードのブロックが他のスレッドによる中断なしに最後まで実行されるようにすることができます。これは、コードブロックの期間中、特定のオブジェクトの相互排他ロックを取得することで実現されます。
そして
Lockキーワードに提供される引数は、参照型に基づくオブジェクトである必要があり、ロックのスコープを定義するために使用されます。
これは、ロックメカニズムがそのオブジェクトのインスタンスを使用して相互排他ロックを作成しているためと考えられます。
これによると MSDNスレッド 、参照変数への変更はすべてのスレッドに表示されず、古い値を使用する可能性があり、AFAIK値型は渡されたときにコピーを作成すると思いますスレッド間。
MSDNから正確に引用するには
割り当てがアトミックであるという事実は、書き込みが他のスレッドによって即座に監視されることを意味するものではないことを明確にすることも重要です。参照が揮発性でない場合は、スレッドが値を更新した後、別のスレッドが参照から古い値を読み取る可能性があります。ただし、更新自体はアトミックであることが保証されています(基本となるポインターの一部が更新されることはありません)。
これは、「マイクロソフトのエンジニアがその方法で実装したため」と回答した場合の1つだと思います。
内部でロックが機能する方法は、メモリ内にロック構造のテーブルを作成し、オブジェクトvtableを使用して、必要なロックがあるテーブル内の位置を記憶することです。これにより、実際にはないにも関わらず、すべてのオブジェクトにロックがあるように見えます。ロックされたものだけが行います。値型には参照がないため、ロック位置を格納するvtableはありません。
Microsoftがこの奇妙な方法を選択した理由は誰にもわかりません。 Monitorをインスタンス化する必要のあるクラスにすることができます。 MSの従業員による記事で、このデザインパターンは間違いだったと思いますが、今は見つけられないようです。