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

xsl*_*xsl 10 c# static multithreading thread-safety

可能重复:
C#自动实现的静态属性是否是线程安全的?

在以下示例类中

static class Shared
{
    public static string[] Values { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Jef*_*dge 10

这种用法是线程安全的:

string[] localValues = Shared.Values;
for (int index = 0; index < localValues.length; index++)
    ProcessValues(localValues[index]);
Run Code Online (Sandbox Code Playgroud)

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

for (int index = 0; index < Shared.Values.Length; index++)
    ProcessValues(Shared.Values[index]);
Run Code Online (Sandbox Code Playgroud)

通过这样做,我宁愿让线程安全调用更自然:

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

当然,用户仍然可以将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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @dthorpe - 重要的是C#语言定义保证对对象的引用将被复制到原子代码中,而不管它运行的平台或机制的实现方式.对于像这样的情况,这是非常有用的.:-) (3认同)
  • 要添加到这个答案:在这个惰性读取方案中不需要锁定,因为对共享数组的引用用作原子操作.即使另一个线程要将新值写入共享变量,从共享变量读取引用(指针)也是线程安全的.这是一种"世代"版本控制的形式 - 每次引用共享数据时,它本质上都是数据的不可变快照,在释放所有对它的引用之前保持活动状态. (2认同)

Jon*_*eet 8

无需任何额外的保护,有没有保证,一个读线程将永远看到新的价值.在实践中,只要阅读线程做了重要的事情,它就会看到新值.特别是,你永远不会看到"半"更新,引用指向死区.

如果你进入这个领域volatile,我相信即使这种危险也会被消除 - 但是无锁编程通常很难解释.当然,这意味着放弃它是一个自动实现的属性.


Luk*_*keH 5

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

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

为了回应你的编辑......

没什么不好将与您现有的代码发生(例如,撕裂读取),但不保证您的读者会不断看到新的价值.例如,可能 - 尽管可能不太可能 - 旧的引用将永远缓存在寄存器中.