【发布时间】:2018-04-18 18:23:19
【问题描述】:
在线程方面尝试了解 .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可以被多次调用(从不同的线程)并且在Dispose被调用之后永远不会被调用。 -
Dispose只会被调用一次。
现在我的问题是,_task 的值(在Dispose 方法中)是否始终是“新”值,这意味着它将从“主存储器”中读取,而不是从寄存器中读取?从我一直在阅读的内容来看,Interlocked 创建了一个完整的内存屏障,所以我假设 _task 将从主内存中读取,还是我完全关闭了?
【问题讨论】:
-
几乎没有理由假设任何代码在启动后都会修改 _task 变量。只能读取的变量不能有错误的值。使用 lock 语句的最大优点是它更容易推理。您将有一些机会避免您在代码中放置的不可调试的线程竞争错误。您无法保证该任务实际上会继续执行添加的任务。
-
阅读您的答案我假设您并没有真正阅读我的问题。首先,如果再次调用 Run,_task 变量会发生变化(看看 Run 方法中的延续)。我很清楚使用锁会更容易调试、读取等,而且我不会在真实场景中使用上述代码。然而,这只是我试图深入研究的一个例子,即整个内存屏障模型。
-
我不会称之为“.NET 的内存模型”。障碍归结为汇编指令 - 跨核心缓存的缓存同步并不完全是 .NET 问题。
-
@TomTom C# 语言对给定变量的行为方式做出了某些保证,您可以将其称为语言内存模型。只要根据语言规范的规则,代码应该可以工作,那么您就不需要关心 C# 语言如何强制执行其内存模型的约束的细节。也就是说,除非您发现 C# 语言中的错误,但通常不会经常遇到这种错误。
-
来自 Joe Albahari 的“C# 中的线程”:“Interlocked 的所有方法都会生成一个完整的栅栏。因此,您通过 Interlocked 访问的字段不需要额外的栅栏——除非它们在其他在没有互锁或锁定的情况下放置在您的程序中。” albahari.com/threading/part4.aspx#_Nonblocking_Synchronization
标签: c# multithreading task-parallel-library memory-barriers