线程和内隐记忆障碍

Tse*_*sef 8 c# multithreading memory-barriers task-parallel-library

在涉及线程时,试图理解.net的内存模型.这个问题是严格的理论问题,我知道它可以通过其他方式解决,例如使用lock或标记_taskvolatile.

以下面的代码为例:

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)

现在做出以下假设:

  1. Run可以多次调用(来自不同的线程),并且在调用之后永远不会Dispose被调用.
  2. Dispose 将被称为恰好一次.

现在我的问题是,_task(在Dispose方法中)的值是否总是一个"新的"值,这意味着它是从"主存储器"中读取而不是从寄存器中读取的?从我一直在阅读的内容Interlocked创建一个完整的围栏内存屏障,所以我假设_task将从主内存中读取或我完全关闭?

Bri*_*eon 1

除了过于宽松地使用“新鲜阅读”一词的复杂性之外,是的,_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按值传递,因此可以将其缓存在寄存器中。我正在设想以下场景。

  • 线程A调用Run
  • 线程 A 执行其他操作_working,导致将其缓存在寄存器中。
  • 线程 B 完成任务并ExchangeContinueWith委托调用。
  • 线程 A 调用Dispose.

在上面的场景中_working,将先更改为 1,然后Dispose更改为 0,然后将其翻转回 1(因为该值已缓存在寄存器中),甚至无需进入该if语句。那时_working可能会处于不一致的状态。

就我个人而言,我认为这种情况不太可能发生,主要是因为我认为不会_working以这种方式进行缓存,特别是如果您始终确保通过互锁操作来保护对其的访问。

如果不出意外的话,我希望它能让您思考无锁技术的复杂性。