螺纹安全工作与静态场

时间:2016-10-29 00:31:46

标签: c# multithreading thread-safety

考虑代码:

class InnerClass
{
    public int State { get; set; }

    public bool Equals(InnerClass other) => State == other.State;

    public override int GetHashCode() => State;

    public override string ToString() => State.ToString();
}

class TestClass
{
    private static InnerClass _innerClass;
    private static readonly object _syncObject = new object();

    public InnerClass Get()
    {
        lock (_syncObject)
        {
            return _innerClass;
        }
    }

    public void Set(InnerClass innerClass)
    {
        lock (_syncObject)
        {
            _innerClass = innerClass;
        }
    }
}

[Fact]
public void TestClassTest()
{
    var firstInnerClass = new InnerClass() {State = 1};
    var secondInnerClass = new InnerClass() {State = 2};

    Assert.NotEqual(firstInnerClass, secondInnerClass);

    for (int i = 0; i < 100000; i++)
    {
        var testClass = new TestClass();
        testClass.Set(firstInnerClass);

        var currentInnerClass = testClass.Get();
        Task.Run(() => testClass.Set(secondInnerClass));

        Assert.Equal(firstInnerClass, currentInnerClass);
    }
}

它不起作用。我知道它与_innerClass字段有关。看起来currentInnerClass指针可以在以后替换。

但为什么下一个代码可以正常工作呢?

class TestClass
{
    private static InnerClass _innerClass;

    public InnerClass Get()
    {
        return Interlocked.CompareExchange(ref _innerClass, _innerClass, _innerClass);
    }

    public void Set(InnerClass innerClass)
    {
        Interlocked.CompareExchange(ref _innerClass, innerClass, null);
    }
}

所以有两个问题:

  1. Interlocked如何与内存一起工作以便它可以正常工作?

  2. 如何更改第一个代码(使用lock运算符)以使其正常?也许MemoryBarrier可以帮助我?我不确定......

1 个答案:

答案 0 :(得分:4)

让我们看一下你在循环中所做的事情:

// Here you are creating a new TestClass. Should be whole new state, right
//Maybe not...
var testClass = new TestClass();

// Here we set the field to firstInnerClass
testClass.Set(firstInnerClass);

// Here we get the field value. Should be firstInnerClass always, right? Maybe...
var currentInnerClass = testClass.Get();

// Here we create a task to run on another thread.
// This will asynchronously update the state. 
// Might take a while though...
Task.Run(() => testClass.Set(secondInnerClass));

// Here we assert that the state we read is what we set
Assert.Equal(firstInnerClass, currentInnerClass);

到目前为止,这么好。我们写一个值,我们读取一个值,我们异步地启动对该值的更改,然后我们确认我们读到的是我们写的内容。

问题在于:

private static InnerClass _innerClass;

static这里表示该字段与类型相关联,而不是与实例相关联。它在所有类的实例中共享。所以正在发生的是,在某些迭代N中,来自迭代N-1的异步任务花费足够长的时间来完成它在写和读之间执行它的更新。因此,读取得到secondInnerClass,而不是firstInnerClass。您正在创建新的TestClass的事实是无关紧要的 - 该类的内容字段是静态的,因此与其他所有副本共享。

如果将TestClass更改为以下内容,则错误应消失:

class TestClass
{
    private InnerClass _innerClass;
    private readonly object _syncObject = new object();

    public InnerClass Get()
    {
        lock (_syncObject)
        {
            return _innerClass;
        }
    }

    public void Set(InnerClass innerClass)
    {
        lock (_syncObject)
        {
            _innerClass = innerClass;
        }
    }
}