可能重复:
Are C# auto-implemented static properties thread-safe?
在以下示例类
中static class Shared
{
public static string[] Values { get; set; }
}
许多读者线程会定期读取Values
字符串数组,而有时单个编写器将使用setter用新值替换整个数组。我需要使用ReaderWriterLock
还是C#会自动处理的?
编辑:在我的情况下,唯一需要的“线程安全性”是:当读者在搜索值时替换数组时,不会发生任何错误。我不在乎读者是否会立即使用新值,只要他们将来会使用它
答案 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的末尾)来确保读者只能看到完全初始化的数组。