Mat*_*r82 7 c# .net-4.0 thread-safety
.NET 4.0的System.Lazy <T>类通过枚举LazyThreadSafetyMode提供三种线程安全模式,我将其概括为:
我想要一个延迟初始化的值,它遵循稍微不同的线程安全规则,即:
只有一个并发线程将尝试创建基础值.成功创建后,所有等待的线程将获得相同的值.如果在创建期间发生未处理的异常,它将在每个等待的线程上重新抛出,但它不会被缓存,后续尝试访问基础值将重新尝试创建并可能成功.
因此,与LazyThreadSafetyMode.ExecutionAndPublication的关键不同之处在于,如果创建时"先行"失败,则可以在以后重新尝试.
是否存在提供这些语义的现有(.NET 4.0)类,还是我必须自己编写?如果我自己滚动是否有一种聪明的方法可以在实现中重用现有的Lazy <T>以避免显式锁定/同步?
注意对于一个用例,假设"创建"可能很昂贵并且容易出现间歇性错误,例如从远程服务器获取大量数据.我不想进行多次并发尝试来获取数据,因为它们可能都会失败或全部成功.但是,如果它们失败了,我希望以后能够重试.
我尝试了达林更新答案的一个版本,该版本没有我指出的竞争条件......警告,我不完全确定这最终完全没有竞争条件。
private static int waiters = 0;
private static volatile Lazy<object> lazy = new Lazy<object>(GetValueFromSomewhere);
public static object Value
{
get
{
Lazy<object> currLazy = lazy;
if (currLazy.IsValueCreated)
return currLazy.Value;
Interlocked.Increment(ref waiters);
try
{
return lazy.Value;
// just leave "waiters" at whatever it is... no harm in it.
}
catch
{
if (Interlocked.Decrement(ref waiters) == 0)
lazy = new Lazy<object>(GetValueFromSomewhere);
throw;
}
}
}
Run Code Online (Sandbox Code Playgroud)
更新:我想我在发布此内容后发现了竞争条件。这种行为实际上应该是可以接受的,只要您能接受一种可能罕见的情况,即在Lazy<T>
另一个线程已经从成功的快速返回之后,某个线程抛出从慢速观察到的异常Lazy<T>
(未来的请求将全部成功)。
waiters
= 0Interlocked.Decrement
( waiters
= 1)之前的运行Interlocked.Increment
( waiters
= 1)之前Interlocked.Decrement
并准备覆盖 ( waiters
= 0)Interlocked.Decrement
( waiters
= 1)之前lazy
用新的覆盖(称为lazy1
)(waiters
= 1)lazy1
( waiters
= 2)Interlocked.Decrement
( waiters
= 1)lazy1
(waiters
现在不相关)我无法想出一系列事件会导致比“此线程在另一个线程产生成功结果后引发异常”更糟糕的情况。
Update2:声明lazy
为volatile
确保所有读者立即看到受保护的覆盖。有些人(包括我自己)看到volatile
并立即想到“好吧,这可能被错误地使用了”,他们通常是对的。这就是我在这里使用它的原因:在上面示例中的事件序列中,t3 仍然可以读取旧的,lazy
而不是lazy1
如果它位于t1 修改为包含的lazy.Value
那一刻的读取之前。 防止这种情况发生,以便下一次尝试可以立即开始。lazy
lazy1
volatile
我还提醒自己为什么我脑子里有这样的想法:“低锁并发编程很难,只需使用 C#lock
语句!!!” 我一直在写原来的答案。
Update3:刚刚更改了Update2中的一些文本,指出了必要的实际情况volatile
——Interlocked
这里使用的操作显然是在当今重要的CPU架构上实现了全围栏,而不是像我最初假设的那样是半围栏,因此volatile
保护的部分比我最初想象的要窄得多。
归档时间: |
|
查看次数: |
2092 次 |
最近记录: |