C#变量新鲜

时间:2015-06-06 00:30:42

标签: c# multithreading task-parallel-library volatile memory-model

假设我在一个类中有一个成员变量(具有原子读/写数据类型):

bool m_Done = false;

后来我创建了一个将其设置为true的任务:

Task.Run(() => m_Done = true);

我不关心将m_Done设置为true的时间。 我的问题是我是否有C#语言规范和任务并行库的保证 如果我从不同的线程访问它,最终m_Done会是真的吗? 例如:

if(m_Done) { // Do something }

我知道使用锁会引入必要的内存障碍,m_Done将在以后显示为true。 此外,我可以在设置变量和Volatile.Read时使用Volatile.Write 阅读它。 我看到很多代码以这种方式编写(没有锁或易失性),我不确定它是否正确。

请注意,我的问题并非针对C#或.Net的特定实现, 它针对的是规范。 如果在x86,x64,Itanium或ARM上运行,我需要知道当前代码的行为是否相似。

2 个答案:

答案 0 :(得分:23)

  

如果将m_Done设置为true,我就不在乎了。我的问题是,我是否有C#语言规范和任务并行库的保证,如果我从其他线程访问它,最终m_Done将成立?

没有

m_Done的读取是非易失性的,因此可以在时间上任意向后移动,并且可以缓存结果。结果,在所有时间的每次阅读中都可以观察到false

  

如果在x86,x64,Itanium或ARM上运行,我需要知道当前代码的行为是否相似。

规范不保证在强(x86)和弱(ARM)内存模型中会观察到代码执行相同的操作。

规范非常明确地说明了对非易失性读写的保证:在没有某些特殊事件(例如锁)的情况下,它们可以在不同的线程上任意重新排序。

阅读规范以获取详细信息,特别是与易失性访问相关的副作用。如果您之后有更多问题,请发布一个新问题。这是非常棘手的事情。

此外,问题预先假定您忽略了确定任务已完成的现有机制,而是滚动您自己的机制。现有机制由专家设计;使用它们。

  

我看到很多代码以这种方式编写(没有锁或易失性),我不确定它是否正确。

几乎肯定不是。

向编写该代码的人提出的一个很好的练习是:

static volatile bool q = false;
static volatile bool r = false;
static volatile bool s = false;
static volatile bool t = false;
static object locker = new object();

static bool GetR() { return r; }  // No lock!
static void SetR() { lock(locker) { r = true; } }

static void MethodOne()
{
  q = true;
  if (!GetR())
    s = true;
}

static void MethodTwo()
{
  SetR();
  if (!q)
    t = true;
}

在初始化字段之后,从一个线程调用MethodOne,从另一个线程调用MethodTwo。请注意,一切都是易失性的,写入r不仅是易失性的,而且是完全隔离的。两种方法都正常完成。之后s和t是否有可能在第一个线程上都被观察到为真?在x86上有可能吗?看来不是;如果第一个线程赢得比赛,则t保持为假,如果第二个线程获胜,则s保持为假;这种分析是错误的。为什么? (提示:如何允许x86重写MethodOne?)

如果编码器无法回答这个问题,那么他们几乎肯定无法使用volatile正确编程,并且不应该在没有锁的情况下跨线程共享内存。

答案 1 :(得分:4)

尝试此代码,构建版本,在没有Visual Studio的情况下运行:

class Foo
{
    private bool m_Done = false;

    public void A()
    {
        Task.Run(() => { m_Done = true; });
    }

    public void B()
    {
        for (; ; )
        {
            if (m_Done)
                break;
        }

        Console.WriteLine("finished...");
    }
}

class Program
{

    static void Main(string[] args)
    {
        var o = new Foo();
        o.A();
        o.B();

        Console.ReadKey();
    }
}

你很有可能看到它永远在运行