Tse*_*sef 8 c# multithreading memory-barriers task-parallel-library
在涉及线程时,试图理解.net的内存模型.这个问题是严格的理论问题,我知道它可以通过其他方式解决,例如使用lock或标记_task为volatile.
以下面的代码为例:
class Test
{
Task _task;
int _working = 0;
public void Run()
{
if (Interlocked.CompareExchange(ref _working, 1, 0) == 0)
{
_task = Task.Factory.StartNew(() =>
{
//do some work...
});
_task.ContinueWith(antecendent => Interlocked.Exchange(ref _working, 0));
}
}
public void Dispose()
{
if (Interlocked.CompareExchange(ref _working, _working, 0) == 1)
{
_task.ContinueWith(antecendent => { /*do some other work*/ });
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在做出以下假设:
Run可以多次调用(来自不同的线程),并且在调用之后永远不会Dispose被调用.Dispose 将被称为恰好一次.现在我的问题是,_task(在Dispose方法中)的值是否总是一个"新的"值,这意味着它是从"主存储器"中读取而不是从寄存器中读取的?从我一直在阅读的内容Interlocked创建一个完整的围栏内存屏障,所以我假设_task将从主内存中读取或我完全关闭?
除了过于宽松地使用“新鲜阅读”一词的复杂性之外,是的,_task将从主存储器中重新获取。但是,您的代码可能存在单独的甚至更微妙的问题。考虑为您的代码采用替代但完全相同的结构,这应该可以更轻松地发现潜在问题。
public void Dispose()
{
int register = _working;
if (Interlocked.CompareExchange(ref _working, register, 0) == 1)
{
_task.ContinueWith(antecendent => { /*do some other work*/ });
}
}
Run Code Online (Sandbox Code Playgroud)
的第二个参数CompareExchange按值传递,因此可以将其缓存在寄存器中。我正在设想以下场景。
Run _working,导致将其缓存在寄存器中。Exchange从ContinueWith委托调用。Dispose.在上面的场景中_working,将先更改为 1,然后Dispose更改为 0,然后将其翻转回 1(因为该值已缓存在寄存器中),甚至无需进入该if语句。那时_working可能会处于不一致的状态。
就我个人而言,我认为这种情况不太可能发生,主要是因为我认为不会_working以这种方式进行缓存,特别是如果您始终确保通过互锁操作来保护对其的访问。
如果不出意外的话,我希望它能让您思考无锁技术的复杂性。
| 归档时间: |
|
| 查看次数: |
379 次 |
| 最近记录: |