C# 收购&;在锁定期间释放语义?

C# 收购&;在锁定期间释放语义?,c#,multithreading,volatile,C#,Multithreading,Volatile,关键字用于保护字段不受某些编译器优化的影响: 对于非易失性字段,对指令进行重新排序的优化技术可能会导致在多线程程序中出现意外和不可预测的结果,这些程序在访问字段时不进行同步,如lock语句(第8.12节)所提供的同步 但是,MSDN似乎没有明确说明是将优化保护仅应用于表达式中使用的对象,还是应用于语句块中的所有语句 如果我有一段代码: lock(obj_something){ boolFoo = true; if(boolIsFriday) doSomething

关键字用于保护字段不受某些编译器优化的影响:

对于非易失性字段,对指令进行重新排序的优化技术可能会导致在多线程程序中出现意外和不可预测的结果,这些程序在访问字段时不进行同步,如lock语句(第8.12节)所提供的同步

但是,MSDN似乎没有明确说明是将优化保护仅应用于
表达式中使用的对象,还是应用于
语句块中的所有语句

如果我有一段代码:

lock(obj_something){
    boolFoo = true;
    if(boolIsFriday)
        doSomething(anything);
}

boolFoo
boolIsFriday
隐式易失性(即使未声明
volatile
)?

否,仅lock语句-直接-保护对“锁定”对象的访问

其要点是,如果在多个位置使用对象作为锁源,那么由于拥有锁,在任何时候都只能运行其中一个代码块(在锁作用域内)

boolFoo
boolIsFriday
只有在同一对象的锁内以独占方式访问时才受保护(在这种情况下,
obj_something

但是,MSDN似乎没有明确说明lock是仅对表达式中使用的对象应用优化保护,还是对语句块中的所有语句应用优化保护

后者

boolFoo和boolIsFriday隐式是否易变(即使未声明为易变)


不完全正确。
volatile
的概念是,每当访问对象时,都会应用内存屏障。
仅对对象的一次使用应用内存屏障。因此,不会有从
lock
语句内部和外部混合(特定类型的已定义)操作的优化,但是如果其他东西在没有
lock
块的情况下访问该变量,您仍然会陷入麻烦。

块内的所有语句都保证作为一个单元有序,关于在持有相同锁的块内作为一个单元执行的任何其他语句。不多也不少

因此,如果你有:

lock (foo) // in one thread
{
  x();
  y();
}

lock (foo) // in another
{
  a();
  b();
}
您可以保证执行顺序是
x y a b
a b x y


你会阻止
xabyy
xayb
,或者
x
a
同时运行并爆炸。

这是一个困难的话题,我建议你从乔·达菲的书开始——这是1000页的纯知识

回答你的问题:不,变量不是“隐式易变的”

除了互斥之外,
lock
提供的是一个获取和释放的围栏

这意味着对编译器、抖动、cpu或两者之间的任何东西可以应用的重新排序优化设置了一些限制

特别是:

获取锁可防止任何内存访问在栅栏之前移动。
释放锁可防止任何内存访问在栅栏后移动


在您的示例中,在获取锁之前,任何线程都无法观察到写入
boolFoo

类似地,
boolisfride
anything
的读取不能使用在获取锁之前读取的值

在释放时,释放锁时,对
boolFoo
的写入必须可见,通过
doSomething
方法执行的任何写入也必须可见


回答您的评论:这并不阻止其他线程重新排序

如果有另一个线程执行此操作:

// do some work while your lock code is running
for ( int i = 0 ; i < ( int ) 1e7 ; i++ ) ;

var copyOfBoolFoo = boolFoo;
//在锁代码运行时执行一些操作
对于(int i=0;i<(int)1e7;i++);
var copyOfBoolFoo=boolFoo;
然后可能
copyOfBoolFoo
false

这是因为在
for
循环和锁代码运行之前,可能会有一些东西向前看,然后决定读取
boolFoo
并缓存该值

线程
B
中没有任何东西可以阻止这种情况,因此它可能会发生


如果您在线程
B
中读取
boolFoo
之前放置了栅栏(例如锁!),则将保证读取最新的值。

让我们仔细看看您的示例,以帮助回答您的问题。我将稍微调整一下您的代码,以便在读取
boolIsFriday
时更加清晰。行为还是一样的。这就是我们所拥有的

lock(obj_something)
{
    boolFoo = true;
    var register1 = boolIsFriday;
    if (register1)
    {
        var register2 = anything;
        doSomething(register2);
    }
}
接下来,我们将用箭头符号来装饰这段代码,以帮助可视化内存屏障的位置。我将使用↑ 箭与箭↓ 分别表示释放围栏和获取围栏的箭头。Volatile写入具有发布语义,因此它们将具有↑ 放在书写之前。Volatile读取具有acquire语义,因此它们将具有↓ 放在阅读之后。把箭头想象成把一切都推离它。任何其他读写操作都不能在箭头上方上下浮动。如果你用我刚才描述的术语来思考,那么这应该准确地反映出记忆障碍在做什么。它也恰好符合规范

在我们开始之前还有几件事要提。首先,虽然不允许其他内存访问越过箭头的头部,但它们仍然可以越过箭头的尾部。同样,这符合本规范的措辞。第二,一旦产生了记忆屏障,它们就会被锁定到位。生成它们的机制,比如易失性读写,仍然可以自由移动。换句话说,箭头不能移动,但相应的读或写可以移动。第三,
产生完整的内存
↑
lock(obj_something)
↓
{
    boolFoo = true;
    var register1 = boolIsFriday;
    if (register1)
    {
        var register2 = anything;
        doSomething(register2);
    }
↑
}
↓
↑
lock(obj_something)
↓
{
    ↑
    boolFoo = true;
    var register1 = boolIsFriday;
    ↓
    if (register1)
    {
        var register2 = anything;
        doSomething(register2);
    }
↑
}
↓
object foo;
lock (foo=bar)
{
  // contents of lock here
}
object foo;
foo = bar;
object expression = foo;
↑
lock (expression)
↓
{
  // contents of lock here
↑
}
↓