让我们仔细看看您的示例,以帮助回答您的问题。我将稍微调整您的代码,以便在读取boolIsFriday
发生时更清楚。行为仍然相同。这就是我们所拥有的。
lock(obj_something)
{
boolFoo = true;
var register1 = boolIsFriday;
if (register1)
{
var register2 = anything;
doSomething(register2);
}
}
接下来,我们将用箭头符号装饰这段代码,以帮助可视化内存屏障的位置。我将使用 ↑ 箭头和 ↓ 箭头分别表示释放栅栏和获取栅栏。易失性写入具有释放语义,因此它们将在写入之前放置一个 ↑ 。易失性读取具有获取语义,因此它们将在读取之后放置一个↓ 。把箭头想象成把一切都推离它。没有其他读取或写入可以在箭头头部上方或下方浮动。如果您以我刚刚描述的术语来考虑它,那么这应该准确地反映内存屏障的作用。它也恰好与规范一致。
在我们开始之前,还有几件事要提及。首先,虽然没有其他内存访问被允许越过箭头的头部,但它们仍然被允许越过尾部。同样,这符合规范的措辞。其次,一旦生成了内存屏障,它们就会被锁定到位。生成它们的机制,如易失性读取或写入,仍然可以自由移动。换句话说,箭头不能移动,但相应的读或写可以。第三,alock
产生一个完整的内存屏障。
锁内的内存访问是否隐式易失?
为了回答这个问题,我将在您的示例中添加箭头符号。
↑
lock(obj_something)
↓
{
boolFoo = true;
var register1 = boolIsFriday;
if (register1)
{
var register2 = anything;
doSomething(register2);
}
↑
}
↓
注意箭头的位置。代替没有其他内存生成器,只有lock
注入它们。现在有几个事实应该是显而易见的。
- 锁块之外的任何内存访问都不能在块内移动。
- 锁块内的任何内存访问都不能移出块外。
- 只要满足长点#2,锁块内的所有内存访问都可以自由移动。
这应该会立即回答您的一个问题。不,boolFoo
并且boolIsFriday
不要从lock
. 他们仍然可以自由移动,尽管现在有一些限制。
那么如果boolFoo
和boolIsFriday
都被标记为volatile
. 让我们来看看会发生什么。
↑
lock(obj_something)
↓
{
↑
boolFoo = true;
var register1 = boolIsFriday;
↓
if (register1)
{
var register2 = anything;
doSomething(register2);
}
↑
}
↓
注意箭头已放置。写入前有一个↑箭头boolFoo
,表示没有其他内存访问可以通过易失性写入向下浮动。同样,读取后有一个↓箭头boolIsFriday
表示没有其他内存访问可以通过易失性读取浮动。现在有两个特殊的事实显而易见。
- 写入
boolFoo
和读取boolIsFriday
可以交换。请注意没有箭头可以防止这种情况发生。
- 写入
boolFoo
可以一直浮动到lock
块的末尾(当然假设这dosomething
不会产生内存屏障。正是 ↑ 箭头结束了锁定块,防止它进一步下降。
- 的读取
boolIsFriday
不能向下浮动很远,因为在某些时候它必须与读取的交换位置anything
。anything
但是,如果发生这种情况,那么这在语义上与在表示 volatile 读取的 ↓ 上方浮动相同。我们已经说过阻止了移动。
锁是否限制表达式或块内容的优化?
最后回答你的另一个问题。该lock
关键字确实对锁定表达式和锁定块本身的内容都有一些影响。让我们来看看这是如何发生的。我设计了一个带有有趣表达式的 lock 语句。
object foo;
lock (foo=bar)
{
// contents of lock here
}
你看到我做了什么吗?我在表达式中放置了一个赋值。如果我们重新组织它以更好地了解这里发生的事情,它会是什么样子。
object foo;
foo = bar;
object expression = foo;
↑
lock (expression)
↓
{
// contents of lock here
↑
}
↓
现在注意以下几点。
- 由于语句生成的↑ ,write to
foo
是不允许下浮的。lock
这是锁表达式影响的一种方式。
- 我们已经讨论过锁块的内容现在对它们的移动施加了约束。
因此,要回答您的问题,lock
关键字对锁定表达式和锁定块的内容都设置了优化约束。