我最近读到了有关内存障碍和重新排序的问题,现在我对此感到困惑。
考虑以下情况:
private object _object1 = null;
private object _object2 = null;
private bool _usingObject1 = false;
private object MyObject
{
get
{
if (_usingObject1)
{
return _object1;
}
else
{
return _object2;
}
}
set
{
if (_usingObject1)
{
_object1 = value;
}
else
{
_object2 = value;
}
}
}
private void Update()
{
_usingMethod1 = true;
SomeProperty = FooMethod();
//..
_usingMethod1 = false;
}
Update
方法;是获取或设置属性之前始终执行的_usingMethod1 = true
语句?或者由于重新订购问题我们无法保证?
我们应该使用volatile
之类的
private volatile bool _usingMethod1 = false;
如果我们使用lock;
,我们可以保证锁中的每个语句都会按顺序执行:
private void FooMethod()
{
object locker = new object();
lock (locker)
{
x = 1;
y = a;
i++;
}
}
答案 0 :(得分:28)
记忆障碍的主题非常复杂。它甚至不时让专家绊倒。当我们谈论记忆障碍时,我们确实将两种不同的想法结合起来。
仅创建两个中的一个的内存屏障有时称为半栅栏。创建两者的内存屏障有时称为全屏。
volatile
关键字会创建半围栏。易失性字段的读取具有获取语义,而写入具有释放语义。这意味着在读取之前或写入之后不能移动指令。
lock
关键字在两个边界(入口和出口)上创建全围栏。这意味着在每个边界之前或之后都不能移动指令。
然而,如果我们只关心一个线程,那么所有这些都没有用。由该线程感知的排序始终保留。事实上,没有基本保证,任何计划都无法正常运作。真正的问题是其他线程如何感知读写。这是你需要关注的地方。
所以回答你的问题:
从一个线程的角度来看......是的。从另一个主题的角度来看......不。
这取决于。这可能有用,但我需要更好地了解你想要实现的目标。
从另一个主题的角度来看......没有。读取和写入可以在锁定边界内自由移动。他们只是无法超越这些界限。这就是为什么其他线程也必须创建内存障碍的原因。
答案 1 :(得分:4)
volatile关键字在这里没有完成任何事情。它有很弱的保证,并不意味着存储障碍。您的代码未显示另一个创建的线程,因此很难猜测是否需要锁定。但是,如果两个线程可以同时执行Update()并使用相同的对象,那么这是一个很难的要求。
请注意,您发布的锁码不会锁定任何内容。每个线程都有自己的“locker”对象实例。你必须使它成为你的类的私有字段,由构造函数或初始化程序创建。因此:
private object locker = new object();
private void Update()
{
lock (locker)
{
_usingMethod1 = true;
SomeProperty = FooMethod();
//..
_usingMethod1 = false;
}
}
请注意,SomeProperty分配也会有竞争。