System.Lazy T具有不同的线程安全模式 [英] System.Lazy<T> with different thread-safety mode

查看:260
本文介绍了System.Lazy T具有不同的线程安全模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

.NET 4.0的 System.Lazy< T> 类通过枚举 LazyThreadSafetyMode ,我将其总结为:

.NET 4.0's System.Lazy<T> class offers three Thread-Safety modes via the enum LazyThreadSafetyMode, which I'll summarise as:


  • LazyThreadSafetyMode.None -不是线程安全的。

  • LazyThreadSafetyMode.ExecutionAndPublication -只有一个并发线程将尝试创建基础线程值。创建成功后,所有等待线程将获得相同的值。如果在创建过程中发生未处理的异常,则将在每个等待的线程上将其重新抛出,在每次访问基础值的后续尝试中将对其进行缓存和重新抛出。

  • LazyThreadSafetyMode.PublicationOnly -多个并发线程将尝试创建基础值,但第一个成功的线程将确定传递给所有线程的值。如果在创建过程中发生未处理的异常,则不会对其进行缓存和并发处理。随后尝试访问基础值将重试创建&可能会成功。

  • LazyThreadSafetyMode.None - Not thread safe.
  • LazyThreadSafetyMode.ExecutionAndPublication - Only one concurrent thread will attempt to create the underlying value. On successful creation, all waiting threads will receive the same value. If an unhandled exception occurs during creation, it will be re-thrown on each waiting thread, cached and re-thrown on each subsequent attempt to access the underlying value.
  • LazyThreadSafetyMode.PublicationOnly - Multiple concurrent threads will attempt to create the underlying value but the first to succeed will determine the value passed to all threads. If an unhandled exception occurs during creation, it will not be cached and concurrent & subsequent attempts to access the underlying value will re-try the creation & may succeed.

我想要一个延迟初始化的值,该值遵循略有不同的线程安全规则,即:

I'd like to have a lazy-initialized value which follows slightly different thread-safety rules, namely:

只有一个并发线程将尝试创建基础值。创建成功后,所有等待线程将获得相同的值。如果在创建过程中发生未处理的异常,它将在每个等待的线程上重新抛出,但不会被缓存,并且随后尝试访问基础值的尝试将重试创建和处理。可能会成功。

所以与 LazyThreadSafetyMode.ExecutionAndPublication 的关键区别在于,如果创建时先行尝试失败可以在以后的时间重新尝试。

So the key differince with LazyThreadSafetyMode.ExecutionAndPublication is that if a "first go" at creation fails, it can be re-attempted at a later time.

是否存在提供这些语义的现有(.NET 4.0)类,还是我必须滚动自己的类?如果我自己动手,是否有一种聪明的方法可以重复使用现有的Lazy< T>为了避免显式锁定/同步?

Is there an existing (.NET 4.0) class that offers these semantics, or will I have to roll my own? If I roll my own is there a smart way to re-use the existing Lazy<T> within the implementation to avoid explicit locking/synchronization?

N.B。对于一个用例,想象一下创建可能很昂贵,并且容易出现间歇性错误,例如从远程服务器获取大量数据。我不想多次尝试获取数据,因为它们很可能全部失败或全部成功。但是,如果它们失败了,我希望以后可以重试。

N.B. For a use case, imagine that "creation" is potentially expensive and prone to intermittent error, involving e.g. getting a large chunk of data from a remote server. I wouldn't want to make multiple concurrent attempts to get the data since they'll likely all fail or all succeed. However, if they fail, I'd like to be able to retry later on.

推荐答案

我尝试的< a href = https://stackoverflow.com/a/34530839/1083771>达林的更新答案,其中没有竞赛条件,我指出了 ...警告,我不能完全确定这是否最终完全摆脱了竞争条件。

My attempt at a version of Darin's updated answer that doesn't have the race condition I pointed out... warning, I'm not completely sure this is finally completely free of race conditions.

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;
        }
    }
}

更新:我以为自己找到了发布此内容后的比赛条件。该行为实际上应该是可以接受的,只要您对一个罕见的情况(在某些情况下某些线程抛出从另一个线程后缓慢的 Lazy< T> 观察到的异常)抛出异常就可以了已经从成功的快速 Lazy< T> 返回(将来的请求将全部成功)。

Update: I thought I found a race condition after posting this. The behavior should actually be acceptable, as long as you're OK with a presumably rare case where some thread throws an exception it observed from a slow Lazy<T> after another thread has already returned from a successful fast Lazy<T> (future requests will all succeed).


  • 侍者 = 0

  • t1:进入运行直到 Interlocked。 侍者 = 1)

  • t2:进入并运行到<$ c之前$ c>互锁。增量(侍者 = 1)

  • t1:执行其 Interlocked.Decrement 并准备覆盖(侍者 = 0)

  • t2:运行到在 Interlocked.Decrement waiters = 1)之前

  • t1:用新的( lazy1 )覆盖 lazy waiters = 1)

  • t3:出现并在 lazy1 上阻止(侍者 = 2)

  • t2:执行其 Interlocked.Decrement 侍者 = 1)

  • t3:从 lazy1 获取并返回值(侍者现在无关紧要)

  • t2:抛出其异常

  • waiters = 0
  • t1: comes in runs up to just before the Interlocked.Decrement (waiters = 1)
  • t2: comes in and runs up to just before the Interlocked.Increment (waiters = 1)
  • t1: does its Interlocked.Decrement and prepares to overwrite (waiters = 0)
  • t2: runs up to just before the Interlocked.Decrement (waiters = 1)
  • t1: overwrites lazy with a new one (call it lazy1) (waiters = 1)
  • t3: comes in and blocks on lazy1 (waiters = 2)
  • t2: does its Interlocked.Decrement (waiters = 1)
  • t3: gets and returns the value from lazy1 (waiters is now irrelevant)
  • t2: rethrows its exception

我无法提出一系列导致比该线程在另一个线程产生成功结果之后引发异常更糟糕的事情的事件。

I can't come up with a sequence of events that will cause something worse than "this thread threw an exception after another thread yielded a successful result".

Update2:声明 lazy volatile ,以确保所有读者都能立即看到受保护的覆盖。有些人(包括我自己)看到 volatile 并立即认为好吧,可能是使用不正确,他们通常是正确的。这就是我在这里使用它的原因:在上面示例中的事件序列中,t3仍然可以读取旧的 lazy 而不是 lazy1 ,如果它刚好位于读取 lazy.Value 之前,则t1修改了 lazy 以包含 lazy1 volatile 可以防止这种情况发生,以便下次尝试可以立即开始。

Update2: declared lazy as volatile to ensure that the guarded overwrite is seen by all readers immediately. Some people (myself included) see volatile and immediately think "well, that's probably being used incorrectly", and they're usually right. Here's why I used it here: in the sequence of events from the example above, t3 could still read the old lazy instead of lazy1 if it was positioned just before the read of lazy.Value the moment that t1 modified lazy to contain lazy1. volatile protects against that so that the next attempt can start immediately.

我也提醒自己为什么这件事在我脑海中回荡着:低锁并发编程很难,只需使用C# lock 语句!!!

I've also reminded myself why I had this thing in the back of my head saying "low-lock concurrent programming is hard, just use a C# lock statement!!!" the entire time I was writing the original answer.

Update3:只是更改了Update2中的一些文本,指出了导致不稳定的实际情况必需的-显然,这里使用的 Interlocked 操作已在当今的重要CPU架构上实现了全栅栏保护,而不是像我最初那样仅用半栅栏实现-假设,因此 volatile 保护的区域比我原来想象的要窄得多。

Update3: just changed some text in Update2 pointing out the actual circumstance that makes volatile necessary -- the Interlocked operations used here are apparently implemented full-fence on the important CPU architectures of today and not half-fence as I had originally just sort-of assumed, so volatile protects a much narrower section than I had originally thought.

这篇关于System.Lazy T具有不同的线程安全模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆