获取并设置一个简单的静态属性线程是否安全?

时间:2010-08-19 16:07:34

标签: c# multithreading static thread-safety

  

可能重复:
  Are C# auto-implemented static properties thread-safe?

在以下示例类

static class Shared
{
    public static string[] Values { get; set; }
}

许多读者线程会定期读取Values字符串数组,而有时单个编写器将使用setter用新值替换整个数组。我需要使用ReaderWriterLock还是C#会自动处理的?

编辑:在我的情况下,唯一需要的“线程安全性”是:当读者在搜索值时替换数组时,不会发生任何错误。我不在乎读者是否会立即使用新值,只要他们将来会使用它

6 个答案:

答案 0 :(得分:10)

此用法是线程安全的:

string[] localValues = Shared.Values;
for (int index = 0; index < localValues.length; index++)
    ProcessValues(localValues[index]);

此用法不是线程安全的,并且可能导致越界异常:

for (int index = 0; index < Shared.Values.Length; index++)
    ProcessValues(Shared.Values[index]);

我更喜欢通过这样的方式使线程安全调用更自然:

static class Shared   
{
    private static string[] values;   
    public static string[] GetValues() { return values; }
    public static void SetValues(string[] values) { Shared.values = values; }
}

当然,用户仍然可以在循环中放置GetValues(),这也差一点,但至少它显然很糟糕。

根据具体情况,更好的解决方案可能是分发副本,这样调用代码就不会改变数组。这就是我通常会做的,但在你的情况下可能不合适。

static class Shared
{
    private static string[] values;
    public static string[] GetValues()
    {
        string[] currentValues = values;
        if (currentValues != null)
            return (string[])currentValues.Clone();
        else
            return null;
    }
    public static void SetValues(string[] values)
    {
        Shared.values = values;
    }
}

答案 1 :(得分:8)

没有任何额外的保护,没有保证读取线程永远看到新值。实际上,只要阅读线程做了重要的事情,它就会 看到新值。特别是,你永远不会看到“半”更新,引用指向死区。

如果您创建字段volatile,我相信即使这种危险也会被删除 - 但无锁编程通常很难解释。当然,这意味着放弃它是一个自动实现的属性。

答案 2 :(得分:5)

这取决于线程安全的含义。

读取和替换保证是原子的,但不能保证写入后的读取必然会读取新值。

回复您的修改...

现有代码(例如,撕裂的读取)不会发生任何不良后果,但无法保证您的读者永远看到新值。例如,可能 - 尽管可能不太可能 - 旧的引用将永远缓存在寄存器中。

答案 3 :(得分:2)

这不是线程安全的。是的,你需要使用锁。

答案 4 :(得分:2)

我会锁定。对于多次阅读,偶尔使用ReaderWriterLockSlim进行写入 - 比ReaderWriteLock更有效。

static class Shared
{
    private static ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
    private static string[] _values;

    public static string[] Values 
    {
        get
        {
            _rwLock.EnterReadLock();
            try
            {
                return _values;
            }
            finally
            {
                _rwLock.ExitReadLock();
            }
        }
        set
        {
            _rwLock.EnterWriteLock();
            try
            {
                _values = value;
            }
            finally
            {
                _rwLock.ExitWriteLock();
            }
        }
    }
}

答案 5 :(得分:1)

稍微偏离主题,Java 2 1.5内存模型增加了两个保证(参见Java theory and practice: Fixing the Java Memory Model, Part 2):

  • 易失性读/写也是内存障碍。
  • final字段在构造函数外部完全初始化。

后者是一个有趣的案例:您曾经能够在一个线程中执行foo = new String(new char[]{'a','b','c'});并在另一个线程中执行foo = "123"; System.out.println(foo)并打印空字符串,因为无法保证写入foo的最终字段将在写入foo之前发生。

我不确定.NET数组初始化的细节;可能需要使用Thread.MemoryBarrier()(在getter的开头和setter的末尾)来确保读者只能看到完全初始化的数组。