volatile关键字用于保护字段免受某些编译器优化的影响:
对于非易失性字段,重新排序指令的优化技术可能导致多线程程序中出现意外和不可预测的结果,这些程序访问没有同步的字段,例如lock-statement提供的字段(第8.12节)
但是,MSDN似乎并未明确lock
是否仅对expression
中使用的对象或statement_block
中的所有语句应用优化保护。
如果我有一段代码:
lock(obj_something){
boolFoo = true;
if(boolIsFriday)
doSomething(anything);
}
boolFoo
和boolIsFriday
是否具有易变性(即使它未被声明为volatile
)?
答案 0 :(得分:2)
不,只有lock语句 - 直接 - 保护对你“锁定”对象的访问。
关键是如果你在多个地方使用一个对象作为锁源,那么由于拥有锁,这些代码块中只有一个(在锁定范围内)可以在任何时间运行。
boolFoo
和boolIsFriday
仅在同一对象的锁定内被访问时才受到保护(在本例中为obj_something
)
答案 1 :(得分:2)
但是,MSDN似乎并未明确锁是否仅对表达式中使用的对象或statement_block中的所有语句应用优化保护。
后者。
boolFoo和boolIsFriday是否存在易变性(即使它没有声明为volatile)?
不完全,不。 volatile
的想法是在访问对象时应用内存屏障。 lock
仅将内存屏障应用于使用对象的。因此,不存在从lock
语句内部和外部混合(某些类型的已定义)操作的优化,但如果其他内容访问没有lock
块的变量,您仍然可能会遇到麻烦
答案 2 :(得分:2)
这是一个很难的主题,我建议你先阅读Joe Duffy的书Concurrent Programming on Windows
- 这是1000页的纯知识。
回答你的问题:没有变量不是“隐式挥发”
除了互斥之外,lock
提供的内容是获取和释放的障碍。
这意味着对编译器,抖动,cpu或其间的任何内容可以应用重新排序优化有一些限制。
特别是:
获取锁定可防止任何内存访问在之前移动围栏。
释放锁定可防止任何内存访问在之后移动。
在您的示例中,在获取锁定之前,任何线程都无法观察到对boolFoo
的写入。
同样,boolIsFriday
和anything
的读取不能使用在获取锁之前读取的值。
在发布时,释放锁时必须显示对boolFoo
的写入,doSomething
方法执行的任何写操作都必须如此。
回答你的评论:这并不妨碍其他线程重新订购。
如果你有另一个线程B
执行此操作:
// do some work while your lock code is running
for ( int i = 0 ; i < ( int ) 1e7 ; i++ ) ;
var copyOfBoolFoo = boolFoo;
然后 可能使copyOfBoolFoo
成为false
。
这是因为在for
循环和锁定代码运行之前可能会向前看,并决定阅读boolFoo
并缓存该值。
线程B
中没有任何内容可以阻止这种情况,因此可能会发生这种情况。
如果您在线程boolFoo
中读取B
之前放置了围栏(例如锁!),那么您将保证读取最新值。
答案 3 :(得分:2)
让我们仔细看看您的示例,以帮助回答您的问题。我将稍微调整一下你的代码,以便在读取boolIsFriday
时更清楚。行为仍然是一样的。所以这就是我们所拥有的。
lock(obj_something)
{
boolFoo = true;
var register1 = boolIsFriday;
if (register1)
{
var register2 = anything;
doSomething(register2);
}
}
接下来,我们将使用箭头符号来装饰此代码,以帮助可视化内存屏障的位置。我将使用↑箭头和↓箭头分别表示 release-fence 和 acquire-fence 。易失性写入具有释放语义,因此它们将在写入之前放置。易失性读取具有获取语义,因此在读取之后它们将被放置。想想箭头就是把一切都推开了。没有其他读或写可以向上或向下浮动箭头。如果您按照我刚才描述的术语来思考它,那么这应该准确地反映出内存障碍正在做什么。它也恰好符合规范。
在我们开始之前还有几件事需要提及。首先,虽然没有其他内存访问允许移过箭头,但仍允许它们移过尾部。同样,这符合规范的措辞。其次,一旦产生记忆障碍,它们就会被锁定到位。生成它们的机制,如易失性读或写,仍然可以自由移动。换句话说,箭头不能移动,但相应的读或写可以。第三,lock
会产生完整的记忆障碍。
锁定内部时内存访问是否隐式易失?
要回答这个问题,我会在你的例子中添加箭头符号。
↑
lock(obj_something)
↓
{
boolFoo = true;
var register1 = boolIsFriday;
if (register1)
{
var register2 = anything;
doSomething(register2);
}
↑
}
↓
注意放置箭头的位置。代替没有其他内存生成器,只有lock
正在注入它们。现在有几个事实应该是显而易见的。
这应该立即回答您的一个问题。不,boolFoo
和boolIsFriday
不会隐式继承lock
的波动率语义。他们仍然可以随意移动,尽管现在有一些限制。
那么,如果boolFoo
和boolIsFriday
都标记为volatile
,那该怎么办?让我们来看看会发生什么。
↑
lock(obj_something)
↓
{
↑
boolFoo = true;
var register1 = boolIsFriday;
↓
if (register1)
{
var register2 = anything;
doSomething(register2);
}
↑
}
↓
注意箭头被放置了。在写入boolFoo
之前有一个↑箭头表示没有其他内存访问可以通过volatile写入向下浮动。同样,在读取boolIsFriday
之后有一个↓箭头表示没有其他内存访问可以通过易失性读取浮动。现在有两个特殊的事实。
boolFoo
的写入和boolIsFriday
的读取。注意没有箭头可以防止这种情况。boolFoo
的写入可能一直浮动到lock
块的末尾(当然假设dosomething
不会产生内存障碍。这是↑箭头结束锁定块,防止它进一步下降。boolIsFriday
的读取不能向下浮动很远,因为在某些时候它必须与读取anything
交换位置。但是,如果发生这种情况,那么在语义上与anything
相同,浮动超过表示易失性读取的↓。我们已经说过阻止了运动。锁定是否限制表达式或块内容的优化?
最后回答你的另一个问题。 lock
关键字对锁定表达式和锁定块本身的内容都有一些影响。让我们来看看这是如何发生的。我用一个有趣的表达式设计了一个锁定语句。
object foo;
lock (foo=bar)
{
// contents of lock here
}
你看到我做了什么吗?我在表达式中放了一个赋值。如果我们对其进行重组以更好地了解这里发生的事情就会是什么样子。
object foo;
foo = bar;
object expression = foo;
↑
lock (expression)
↓
{
// contents of lock here
↑
}
↓
现在请注意以下事项。
foo
语句生成的↑,不允许写入lock
。这是锁定表达受影响的一种方式。 因此,要回答您的问题,lock
关键字会对锁定表达式和锁定块的内容设置优化约束。
答案 4 :(得分:0)
对于作为一个单元,在块内执行的任何其他语句,保证块内的所有语句都是有序的。不多也不少。
所以如果你有:
lock (foo) // in one thread
{
x();
y();
}
lock (foo) // in another
{
a();
b();
}
您可以保证执行顺序为x y a b
或a b x y
。
您阻止同时运行x a b y
,x a y b
或x
和a
并且爆炸。