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