在"C#4 in a Nutshell"中,作者表明这个类有时可以写0 MemoryBarrier但是我无法在我的Core2Duo中重现:
public class Foo
{
int _answer;
bool _complete;
public void A()
{
_answer = 123;
//Thread.MemoryBarrier(); // Barrier 1
_complete = true;
//Thread.MemoryBarrier(); // Barrier 2
}
public void B()
{
//Thread.MemoryBarrier(); // Barrier 3
if (_complete)
{
//Thread.MemoryBarrier(); // Barrier 4
Console.WriteLine(_answer);
}
}
}
private static void ThreadInverteOrdemComandos()
{
Foo obj = new Foo();
Task.Factory.StartNew(obj.A);
Task.Factory.StartNew(obj.B);
Thread.Sleep(10);
}
Run Code Online (Sandbox Code Playgroud)
这种需要对我来说似乎很疯狂.如何识别出现这种情况的所有可能情况?我认为如果处理器改变了操作顺序,它需要保证行为不会改变.
你还懒得使用障碍吗?
我想了解如何编写线程安全代码.
例如,我在我的游戏中有这个代码:
bool _done = false;
Thread _thread;
// main game update loop
Update()
{
// if computation done handle it then start again
if(_done)
{
// .. handle it ...
_done = false;
_thread = new Thread(Work);
_thread.Start();
}
}
void Work()
{
// ... massive computation
_done = true;
}
Run Code Online (Sandbox Code Playgroud)
如果我理解正确,可能会发生主游戏线程和我_thread可以拥有自己的缓存版本_done,并且一个线程可能永远不会_done在另一个线程中看到更改?
如果可能,如何解决?
是否可以解决,只应用volatile关键字.
或者是有可能读取和写入,通过价值Interlocked的类似的方法Exchange和Read?
如果我包围_done读写操作lock (_someObject),需要我使用Interlocked什么东西来防止缓存?
编辑1
我读过很多关于volatile和VoletileRead(ReadAcquireFence)的矛盾信息(msdn,SO等).
我理解那些内存访问重新排序的限制含义 - 我仍然完全混淆的是新鲜度保证 - 这对我来说非常重要.
(...)这可确保始终在字段中显示最新值.
读取volatile字段称为volatile读取.易失性读取具有"获取语义"; 也就是说,保证在指令序列之后发生的任何内存引用之前发生.
public static int VolatileRead(ref int address)
{
int ret = address;
MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way.
return ret;
}
Run Code Online (Sandbox Code Playgroud)
根据msdn MemoryBarrier doc内存屏障阻止重新排序.然而,这似乎对新鲜度没有任何影响 - 对吗?
如何获得新鲜度保证?标记字段volatile与使用VolatileRead和VolatileWrite语义访问它之间有区别吗?我目前正在执行我的性能关键代码,需要保证新鲜度,但读者有时会得到陈旧的价值.我想知道是否标记状态不稳定将使情况不同.
EDIT1:
我正在努力实现的目标 - 保证读者线程将尽可能获得共享变量(由多个编写者编写)的最新值 - 理想情况下不会超过上下文切换或其他可能推迟立即操作的操作的成本写国家.
如果挥发性或更高级别的构造(例如锁定)具有这种保证(是吗?)而不是它们如何实现这一点?
EDIT2:
非常简洁的问题应该是 - 如何在读取过程中尽可能保证新的价值?理想情况下没有锁定(因为不需要独占访问,并且存在高争用的可能性).
从我在这里学到的东西,我想知道这可能是解决方案(求解(?)行标有注释):
private SharedState _sharedState;
private SpinLock _spinLock = new SpinLock(false);
public void Update(SharedState newValue) …Run Code Online (Sandbox Code Playgroud) 像许多其他人一样,我一直对易失性读/写和围栏感到困惑.所以现在我想完全理解这些是做什么的.
因此,易失性读取应该(1)表现出获取语义,(2)保证读取的值是新鲜的,即它不是缓存值.让我们关注(2).
现在,我已经读过,如果你想执行一个易失性读取,你应该在读取后引入一个获取栅栏(或一个完整的栅栏),如下所示:
int local = shared;
Thread.MemoryBarrier();
Run Code Online (Sandbox Code Playgroud)
这究竟是如何阻止读取操作使用以前缓存的值?根据栅栏的定义(不允许读取/存储在栅栏上方/下方移动),我会在读取之前插入栅栏,防止读取穿过栅栏并及时向后移动(也就是说,是缓存).
如何防止读取被及时向前移动(或后续指令被及时向后移动)保证了易失性(新鲜)读取?它有什么用?
类似地,我认为易失性写入应该在写入操作之后引入栅栏,从而阻止处理器及时向前移动写入(也就是说,延迟写入).我相信这会使处理器刷新对主存储器的写入.
但令我惊讶的是,C#实现在写入之前引入了栅栏!
[MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations
public static void VolatileWrite(ref int address, int value)
{
MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way.
address = value;
}
Run Code Online (Sandbox Code Playgroud)
更新
根据这个例子,显然是从"坚果壳中的C#4"中取出的,在写入之后放置的栅栏2 应该强制写入立即刷新到主存储器,并且在读取之前放置的栅栏3 应该保证一个新的阅读:
class Foo{
int _answer;
bool complete;
void A(){
_answer = 123;
Thread.MemoryBarrier(); …Run Code Online (Sandbox Code Playgroud) 通过锁定助手,我指的是可以通过using语句实现锁定的一次性对象.例如,考虑Jon Skeet的MiscUtilSyncLock类的典型用法:
public class Example
{
private readonly SyncLock _padlock;
public Example()
{
_padlock = new SyncLock();
}
public void ConcurrentMethod()
{
using (_padlock.Lock())
{
// Now own the padlock - do concurrent stuff
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在,考虑以下用法:
var example = new Example();
new Thread(example.ConcurrentMethod).Start();
Run Code Online (Sandbox Code Playgroud)
我的问题是这个 - 因为example是在一个线程上创建并ConcurrentMethod在另一个线程上调用,所以ConcurrentMethod线程不会忘记_padock在构造函数中的赋值(由于线程缓存/读写重新排序),因而抛出一个NullReferenceException(在_padLock本身)?
我知道锁定Monitor/ lock具有内存障碍的好处,但是当使用这样的锁定助手时,我无法理解为什么会有这样的障碍得到保证.在这种情况下,据我所知,构造函数必须修改:
public Example()
{
_padlock = new SyncLock();
Thread.MemoryBarrier();
}
Run Code Online (Sandbox Code Playgroud)
来源: …
来自O'Reilly的C#in a Nutshell:
class Foo
{
int _answer;
bool _complete;
void A()
{
_answer = 123;
Thread.MemoryBarrier(); // Barrier 1
_complete = true;
Thread.MemoryBarrier(); // Barrier 2
}
void B()
{
Thread.MemoryBarrier(); // Barrier 3
if (_complete)
{
Thread.MemoryBarrier(); // Barrier 4
Console.WriteLine (_answer);
}
}
}
Run Code Online (Sandbox Code Playgroud)
假设方法A和B在不同的线程上并发运行:
作者说:"障碍1和4阻止这个例子写"0".障碍2和3提供了新鲜度保证:他们确保如果B在A之后运行,则读取_complete将评估为真.
我的问题是:
这是一个简单的问题,但在阅读之后我为什么需要内存屏障?我很困惑.
在下面的示例中,假设不同的线程重复调用Increment和Counter:
class Foo{
int _counter=0;
public int Counter
{
get { return _counter; }
}
public void Increment()
{
Interlocked.Increment(ref _counter);
}
}
Run Code Online (Sandbox Code Playgroud)
对不起,如果我误解了为什么我需要内存屏障?但似乎这表明上述课程在阅读_counter的价值时可能无法提供新鲜度保证.重复访问Counter属性的线程是否会永远停留在旧的Counter值(因为它缓存在寄存器中)?
在return _counter;必要之前是内存屏障还是锁?
c# multithreading thread-safety shared-memory memory-barriers