线程与内隐记忆障碍

时间:2014-05-26 14:31:20

标签: c# multithreading task-parallel-library memory-barriers

在线程方面试图了解.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*/ });
        }
    }
}

现在做出以下假设:

  1. Run可以被多次调用(来自不同的线程),并且在调用Dispose之后永远不会被调用。
  2. Dispose只会被调用一次。
  3. 现在我的问题是,_task(在Dispose方法中)的价值是否始终为"新鲜"值,意思是它将从"主存储器中读出"而不是从寄存器中读取?从我正在阅读的内容Interlocked创建一个完整的围栏内存屏障,所以我假设_task将从主内存中读取或我完全关闭?

2 个答案:

答案 0 :(得分:1)

我不用C#编码,但如果采用完全内存屏障,那么你的解释是正确的。编译器不应该重用存储在寄存器中的值,而应该以确保内存排序障碍不掩盖内存子系统中存在的实际值的方式获取它。

我也找到了这个答案,清楚地解释了事实确实如此,所以你读过的文件似乎是正确的:Does Interlocked.CompareExchange use a memory barrier?

答案 1 :(得分:1)

除了使用短语" new read"的错综复杂之外。太松散然后是,_task将从主存中重新获取。但是,您的代码可能存在单独的甚至更微妙的问题。考虑一个替代但完全等效的代码结构,这样可以更容易地发现潜在的问题。

public void Dispose()
{
    int register = _working;
    if (Interlocked.CompareExchange(ref _working, register, 0) == 1)
    {
        _task.ContinueWith(antecendent => { /*do some other work*/ });
    }
}

CompareExchange的第二个参数按值传递,因此可以将其缓存在寄存器中。我正在设想以下场景。

  • 主题A调用Run
  • 线程A使用_working执行其他操作,使其将其缓存在寄存器中。
  • 线程B完成任务并从Exchange委托中调用ContinueWith
  • 主题A调用Dispose

在上面的场景中,_working会更改为1然后是0,然后是Dispose将其翻转回1(因为该值已缓存在寄存器中),甚至没有进入{{1}声明。此时if可能处于不一致状态。

就个人而言,我认为这种情况极不可能,因为我认为_working不会以这种方式缓存,特别是如果你总是确保通过互锁操作保护对它的访问。

如果没有别的,我希望它能让你对无锁技术的复杂程度有所了解。