なぜ値型をロックできないのですか?

次のエラーが発生したときにブール変数をロックしようとしていました。

'bool'はlock文で必要とされる参照型ではありません

lock ステートメントでは参照型のみが許可されているようですが、その理由を理解できていません。

Andreasはコメントに記載しています。

1つのスレッドから別のスレッドに[値型]オブジェクトが渡されると、コピーが作成されるため、スレッドは2つの異なるオブジェクト上で動作します。これは安全です。

本当ですか?これは、私が次のことをしたときに、 xToTrue メソッドと xToFalse メソッドで2つの異なる x

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 ではなく、なぜと関連しています。

47
@MartinBrown:私は知っているし、私の本当のコードでは、私は専用オブジェクトのロックを使用します(私の質問で言及したように、なぜ 方法)。 volatile に関しては、正しくロックする必要はありません
追加された 著者 Otiel,
あなたのプログラムは、共有コピーを使用しているスレッドの間でxを渡していません。ただし、ロックのスコープ内でxにアクセスしていないため、xはvolatileと宣言されていないため、スレッドセーフではありません。私は別の質問をしています: "なぜこの例のxは揮発性である必要がありますか?"
追加された 著者 Martin Brown,

9 答え

ちょうどここで野生の推測...

しかし、コンパイラが値型をロックする場合は、何もロックしません。値の型を lock に渡すたびに、それ;別の箱入りのコピー。したがって、ロックは全く異なるオブジェクトであるようになります。 (彼らが実際にいるので)

object 型のパラメータに値型を渡すと、参照型にボックス化(ラップ)されます。これは、これが起こるたびに新しいオブジェクトにします。

36
追加された
それは良い点です。私は編集します。
追加された 著者 Andrew Barber,
キーポイントは、値の型は毎回異なるオブジェクトにボックス化されるということです(ボクシングはコピーと全く同じではなく、私はOPと今後の読者にとっては注目に値すると思います)。 私の答えを参照してください。
追加された 著者 George Duckett,
コンパイラは、値の型をロックするときに、目に見えない固定参照型を作成する可能性があります。
追加された 著者 drowa,

同期ルートレコードを持たないため、値タイプをロックできません。

ロックは、一度に1つのスレッドのみがアクセスできるレコードを持つオブジェクトに依存するCLRおよびOS内部メカニズム(ブロック・ブロック・シンク)によって実行されます。参照型は次のようになります。

  • あるタイプへのポインタ
  • ブロックブロックの同期
  • ヒープ内のインスタンスデータへのポインタ
25
追加された

次のように展開されます。

System.Threading.Monitor.Enter(x);
try {
   ...
}
finally {
   System.Threading.Monitor.Exit(x);
}

それらはコンパイルされますが、 Monitor.Enter / Exit は参照型を必要とします。 と Exit は異なるオブジェクトで動作します。

MSDN メソッドの入力ページから:

値の種類ではなく、オブジェクト(参照型)をロックするには、Monitorを使用します。値型変数をEnterに渡すと、オブジェクトとしてボックス化されます。同じ変数をEnterに再度渡すと、別のオブジェクトとして囲まれ、スレッドはブロックされません。この場合、Monitorが想定していると思われるコードは保護されません。さらに、変数をExitに渡すと、さらに別のオブジェクトが作成されます。 Exitに渡されたオブジェクトがEnterに渡されたオブジェクトと異なるため、MonitorはSynchronizationLockExceptionをスローします。詳細については、「モニター」という概念トピックを参照してください。

16
追加された
+1していただきありがとうございます。
追加された 著者 Andrew Barber,
申し訳ありません、ただ編集しました。
追加された 著者 George Duckett,
@AndersForsgren:明確にするために編集されました。
追加された 著者 George Duckett,
なぜ Monitor.Enter Monitor.Exit に参照型が必要なのでしょうか? (それは実際にOPが後になっているので、尋ねる明らかな質問のように思えます)。
追加された 著者 Oded,
コンパイラでチェックされていないときは...参照型が正しく動作する必要があり、チェックされていないボックス型の値の場合は例外がスローされることもあります。しかしそれはうまくコンパイルされます。コンパイラによって行われる参照型チェックは、lock()ステートメントのみです。
追加された 著者 Anders Forsgren,
最近では、try {}の中に入力するように実際に拡張されています。申し訳ありません、ちょうどニックピッキングしています。
追加された 著者 Anders Forsgren,

概念的になぜこれが許可されていないのかを尋ねているのであれば、その答えは値タイプのアイデンティティがその(これが値型になります)。

だから、 int 4 について話している宇宙の誰もが同じことを話していますそれ?

4
追加された
それは興味深い点です。特に、誰かが尋ねると、「オブジェクト型のパラメータに渡されたときに値型がboxedになるので、ロックにも特定の値型を受け入れるのはなぜですか?」...そうするために、ロックは値になります変数。それは役に立たないだろう。 +1
追加された 著者 Andrew Barber,

.Netチームが開発者を制限し、Monitorが参照のみで動作できるようにしたのはなぜだろうかと思っていました。まず、ロック目的のためだけに専用のオブジェクト変数を定義するのではなく、 System.Int32 に対してロックするのが良いと思っています。

しかし、言語によって提供されるすべての機能は、開発者にとって有用なだけでなく、強力なセマンティクスをもたなければならないようです。したがって、値型を持つセマンティクスは、コードに値型が現れるたびに式の値が評価されるということです。したがって、意味論的な観点から、もし `lock(x) 'と書かれ、xはプリミティブ型であれば、もっと重要なコードagaistのブロックをロックしてより多くの奇妙なよりも、確かに:)。一方、コード中のref変数に会うときは、「ああ、オブジェクトへの参照です」と思っており、参照がコードブロック、メソッド、クラス、スレッドやプロセス間で共有されることを意味し、したがって、ガード。

2つの言葉では、値の型の変数は、各式のそれぞれの実際の値だけを評価するためにコード内に表示されます。

私はそれが主要なポイントの1つだと思います。

4
追加された
+1簡単に説明すると、あなたは@martinブラウンにも答えました
追加された 著者 dotnetguy,

値型には、ロックステートメントがオブジェクトをロックするために使用する同期ブロックがないためです。参照型だけが型情報、同期ブロックなどのオーバーヘッドを運ぶ。

参照型をボックスに入れると、値型を含むオブジェクトがあり、オブジェクトには余分なオーバーヘッドがあるため、そのオブジェクトをロックできます(ロックに使用されるシンクブロックへのポインタ、型情報へのポインタなど)。他の誰もが言っているように - あなたがボックスにオブジェクトを入れると、毎回別のオブジェクトにロックされるように、それを箱に入れるたびに新しいオブジェクトが得られます - ロックを取る目的を完全に破ります。

これはおそらく動作します(それは完全に無意味ですが、試していません)

int x = 7;
object boxed = (object)x;

//thread1:
lock (boxed){
 ...
}
//thread2:
lock(boxed){
...
}

誰もがboxedを使用している限り、ボックス化されたオブジェクトをロックしているので、ボックス化されたオブジェクトは一度しか設定されません。しかし、これをやってはいけません。それは単なる思考練習です(私が言ったように、私はそれをテストしていません)。

あなたの2番目の質問 - いいえ、値は各スレッドのコピーされません。どちらのスレッドも同じブール値を使用しますが、スレッドは最新の値を保証するものではありません(一方のスレッドがすぐにメモリ位置に書き戻されない値を設定すると、値を読み取る他のスレッドは「古い」結果)。

2
追加された
私の2番目の質問にお返事ありがとうございます。それは私には奇妙に聞こえました。値は各スレッドごとにコピーされます。
追加された 著者 Otiel,

以下はMSDNから取ったものです:

ロック(C#)ステートメントとSyncLock(Visual Basic)ステートメントを使用して、コードブロックが他のスレッドによって中断されることなく完了するようにすることができます。これは、コードブロックの持続時間の間、与えられたオブジェクトに対して相互排他ロックを得ることによって達成される。

そして

The argument provided to the lock keyword must be an object based on a reference type, そしてis used to define the scope of the lock.

ロック機構がそのオブジェクトのインスタンスを使用して相互排他ロックを作成するため、これが一部であると想定します。

1
追加された
異なるスレッドで使用されている場合、値の型はコピーされません。これはmemryと同じアドレスです。あなたが失効した値を得る理由は、あるスレッドで設定した値がすぐにメモリ位置に書き戻されない可能性があるからです.JITはそれを見ることができるのでレジスタに 'キャッシュされる'すぐにもう一度使うつもりです。
追加された 著者 Russell Troywest,
ありがとう、私はそれを知らない、私はあなたが大好き
追加された 著者 Vamsi,

私は、なぜこれがMicrosoftのエンジニアがそのように実装したのかという理由で答えが出てくるケースの1つだと思います。

フードの下でのロックの仕組みは、メモリ内にロック構造のテーブルを作成し、オブジェクトvtableを使用して、必要なロックが存在するテーブル内の位置を記憶することです。これにより、実際にはオブジェクトがロックされていないときに、すべてのオブジェクトがロックされるような外観が得られます。ロックされているものだけが行います。値型には参照がないため、ロック位置を格納するvtableはありません。

なぜマイクロソフトがこの奇妙なことをやったのかを誰が推測したのか彼らはあなたがインスタンス化しなければならなかったクラスをMonitorにすることができました。私はMSの従業員が反射でこのデザインパターンは間違いだと言った記事を見たことは確かですが、今はそれを見つけることができません。

0
追加された
(1)ファイナライザを必要とする、(2)入力されたが残っていない場合にリソースをリークする、または(3)放棄された場合にクリーンアップのために他のGCサポートを必要とする、ということです。私の推測では、MSがアプローチ#3を決定し、すべてのクラスオブジェクトがコストを均等に課すように実装したため、すべてのクラスオブジェクトのロックを許可するという技術的な障害はありませんでした。
追加された 著者 supercat,