2011-05-25 30 views
3

首先,让我解释一下情况。允许只有一个线程通过/不使线程等待不必要

我有了2个属性的类:DataA的和数据B;不管它们是什么,重要的是每一个都可以从另一个中计算出来。我处于多线程环境中,并且在需要时计算DataA/DataB(如果有必要,并不总是这样)。我首先想到的是像...

public SomeDataObject DataA 
{ 
    get 
    { 
     if (dataAisAvailable) 
     { 
      return dataA; 
     } 
     else 
     { 
      if (dataBisAvailable) 
      { 
       lock (dataACalcLock) 
       { 
        // Don't want other threads recalculating dataA 
        if (dataAisAvailable) 
        { 
         return dataA; 
        } 

        //////////////////////////////// 
        // Calculate dataA from dataB // 
        //////////////////////////////// 

        dataAisAvailable = true; 
        return dataA; 
       } 
      } 
      else 
      { 
       return null; 
      } 
     } 
    } 
} 

有了这个模式,假设数据B确实可用,在到达现场的第一个线程(我们称之为线程1)变为计算数据A,都好到目前为止...但是,任何等待dataA被计算的线程现在都必须逐一访问它......不是非常高效的imho。我想让这些线程等待thread1来完成dataA的计算,然后所有的线程都像没有锁一样。

我对ManualResetEvents也许有过其他想法,但我不确定如何安全地确保一个线程和一个线程完成计算。

我希望我已经解释清楚,虽然我不相信我有。虽然很乐意澄清......

编辑:我的不好,我在.NET 4.0上。这是在Silverlight ...

回答

1

由后来取消了对我的问题评论的启发,我检查了Lazy<>,并用类似的东西去......

static SomeDataObject DefaultData; 

    private Lazy<SomeDataObject> dataA = new Lazy<SomeDataObject>(() => DefaultData, LazyThreadSafetyMode.ExecutionAndPublication); 
    private Lazy<SomeDataObject> dataB = new Lazy<SomeDataObject>(() => DefaultData, LazyThreadSafetyMode.ExecutionAndPublication); 

    public SomeDataObject DataA 
    { 
     get 
     { 
      return dataA.Value; 
     } 
     set 
     { 
      dataA = new Lazy<SomeDataObject>(() => value, LazyThreadSafetyMode.ExecutionAndPublication); 
      dataB = new Lazy<SomeDataObject>(GetDataB, LazyThreadSafetyMode.ExecutionAndPublication); 
     } 
    } 
    public SomeDataObject DataB 
    { 
     get 
     { 
      return dataB.Value; 
     } 
     set 
     { 
      dataB = new Lazy<SomeDataObject>(() => value, LazyThreadSafetyMode.ExecutionAndPublication); 
      dataA = new Lazy<SomeDataObject>(GetDataA, LazyThreadSafetyMode.ExecutionAndPublication); 
     } 
    } 

    private SomeDataObject GetDataA() 
    { 
     if (DefaultData == dataB.Value) 
     { 
      return null; 
     } 

     //////////////////////////////// 
     // Calculate dataA from dataB // 
     // and return it.    // 
     //////////////////////////////// 
    } 
    private SomeDataObject GetDataB() 
    { 
     if (DefaultData == dataA.Value) 
     { 
      return null; 
     } 

     //////////////////////////////// 
     // Calculate dataA from dataB // 
     // and return it.    // 
     //////////////////////////////// 
    } 

可惜ReaderWriterSlimLock不可用在Silverlight中,否则它看起来很有希望。有一点基准测试表明,上述内容比我自己写的任何内容都要快得多(更不用说做更多我想要的东西了)。

0

建议:

if (dataAisAvailable) 
     { 
      //Wait for AutoResetEvent here, perhaps add a timeout and when it expires, you can return the current dataA, so threads don't wait forever. 
      return dataA; 
     } 
     else 
     { 
      if (dataBisAvailable) 
      { 
       lock (dataACalcLock) 
       { 
        // Don't want other threads recalculating dataA 
        if (dataAisAvailable) 
        { 
         return dataA; 
        } 

        //////////////////////////////// 
        // Calculate dataA from dataB // 
        //////////////////////////////// 

        dataAisAvailable = true; 
        //Set AutoResetEvent to signalled so waiting threads can get to DataA. 
        return dataA; 
       } 
      } 
+0

正在计算dataA的线程将不会到达ResetEvent,因为在thread1完成计算之前dataA不可用。 – 2011-05-25 11:09:03

2

这听起来很像为其ReaderWriterLockSlim被发明(允许多个并发读者的情况下,只有一次一个线程被允许写)。

它可能看起来像这些方针的东西(没有正确验证这个代码,所以一定要确保它的工作原理,只要你想它,你应该决定使用它):

private ReaderWriterLockSlim dataLock = new ReaderWriterLockSlim(); 

public SomeDataObject DataA 
{ 
    get 
    { 
     if (dataAisAvailable) 
     { 
      return dataA; 
     } 

     dataLock.EnterReadLock(); 

     try 
     { 
      if (dataBisAvailable) 
      { 
       dataLock.EnterUpgradeableReadLock(); 

       try 
       { 
        // Don't want other threads recalculating dataA 
        if (dataAisAvailable) 
        { 
         return dataA; 
        } 

        dataLock.EnterWriteLock(); 
        try 
        { 
         //////////////////////////////// 
         // Calculate dataA from dataB // 
         //////////////////////////////// 

         dataAisAvailable = true; 
        } 
        finally 
        { 
         dataLock.ExitWriteLock(); 
        } 

        return dataA; 
       } 
       finally 
       { 
        dataLock.ExitUpgradeableReadLock(); 
       } 
      } 
      else 
      { 
       return null; 
      } 
     } 
     finally 
     { 
      dataLock.EnterReadLock(); 
     } 
    } 
} 

提示/插头:如果你想减少添加的try/finally构造的数量,你可以用扩展方法(as presented in my blog)包装一些,或者通过将其包装在IDisposable代理(as suggested by Josh Perry)中甚至更干净。

+0

如果OP使用.NET 4.0 ... – 2011-05-25 11:08:22

+0

@Tony:...或者。 NET 3.5。 – 2011-05-25 11:12:38

+0

感谢您的代码,非常感谢。看起来我不能在Silverlight中使用这个...虽然这很烦人。 (意识到我以前没有提到过,对不起!) – 2011-05-25 11:18:50

0

可以肯定的 - 你想要做的是避免重新计算A和B,同时在第一次计算后访问它们时避免锁定,是的?

如果一个线程读取dataAisAvailable并且发现它为true,那么就没有问题 - 线程可以使用A.如果它读取dataAisAvailable并且发现它为false,那么就存在一个问题,它需要获得一个排它锁以确保那dataAisAvailable仍然是假的,如果是的话就计算它。我认为,一个关键部分会做。如果A/B花费很长时间来计算,这将回退到内核锁定,但这只会在线程第一次发现其中一个布尔值错误时才会发生。

我认为你可以摆脱这种情况,因为布尔只会从单向到单向,所以你可以用一个简单的布尔检查(我认为)逃脱。

你的'线程1'会发现dataAisAvailable为false,因此试图获取该锁。如果成功,它会再次检查锁内的dataAisAvailable,并对其进行计算或不计算。然后它退出锁定,返回dataA。如果'线程2'首先进入,在线程1检查dataAisAvailable和线程1获取锁之间,计算dataA并退出锁,则线程1将进入锁,发现dataAisAvailable现在为true,因此只需使用dataA退出锁。

线程2-N将始终将dataAisAvailable视为true并获取A而不尝试获取锁。

RGDS, 马丁

+0

我担心的情况是thread1进入锁和dataA变得可用之间到达的大量线程。这些线程将全部等待输入锁,然后将获得锁并逐个返回数据。这是他们获取锁和访问关注我的一个接一个的性能问题... – 2011-05-25 12:21:35

+0

也许我只是ott虽然...哦,是的,你所描述的正是这种情况.. 。 – 2011-05-25 12:26:32