线程安全的一次性计算最佳实践

时间:2009-04-05 10:00:15

标签: .net multithreading caching locking

我需要一个需要计算和缓存的属性,这是很常见的。

通常我会使用锁和布尔顶部检查是否已处理。有时我会在访问者中这样做。

这种方法的性能影响是什么?有没有更好的方法呢。

我对此的常用方法的示例代码:

   Sub Main()
        Dim X AS New X()

        For i AS Integer = 0 To 50
            Dim Thr AS New Threading.Thread(ADdressOF X.ProcessData )
            Thr.Start()
        Next

    End Sub

Private Class X

    Private DataCached AS Boolean 
    Private ProcessedData AS String 
    Private Lock AS New Object()
    Public Function ProcessData() AS String

    Synclock Lock
        IF NOT DataCached Then
            DataCached = True
            ProcessedData = DoStuff()
        End If
    End Synclock

        Console.Writeline(ProcessedData)        
        Return ProcessedData
    End Function


    Function DoStuff() AS String 
        Threading.Thread.Sleep(1000)
        Console.Writeline("Processed")
        return "stuff"
    End Function

End Class

编辑:

这是需要在访问时计算的东西,因为它不断变化。构造函数计算在这里没有帮助。 (示例是我正在做的的真正简化版本)

4 个答案:

答案 0 :(得分:2)

您可以通过双重检查优化来提高并发性:

If Not DataCached Then
    Synclock Lock
    If Not DataCached Then
        ProcessedData = DoStuff()
        DataCached = True ' Set this AFTER processing
    End If
End Synclock

这将避免在第一个init之后的关键部分。

答案 1 :(得分:2)

从不计算两次是否至关重要?即如果两个线程发生同时要求它,并独立计算该值,那是一个显示阻止?在大多数情况下,它不是 - 在这种情况下,只检查null(因为它是一个字符串):( C#中的例子,道歉):

   if(processedData == null) {
       processedData = DoStuff();
   }
   return processedData;

所有后续调用应该会看到新值(如果隐藏在属性/方法中,我认为我们不需要volatile)。

这具有无锁且简单的优点。

另一个技巧是使用嵌套类的静态属性:

string SomeValue {
   get {return MyCache.SomeValue;}
}
static class MyCache {
    public static readonly string SomeValue;
    static MyCache() {
         SomeValue = DoStuff();
    }
}

这是懒惰计算的,但静态初始化程序的规则意味着它只保证运行一次(不包括反射)。

答案 2 :(得分:0)

这是唯一的方法,可能还有其他系统库可以做到这一点,但最终该库也会在内部做同样的事情。

答案 3 :(得分:0)

Firslty,我会在您的类之外移动包含业务逻辑的缓存,并保持您的业务逻辑纯,并允许您独立于应用程序控制缓存。但那不是你的问题......

你需要没有提及你是否会多次冒险,可能会多次计算这些东西,直到缓存很热。简单的方法是:

if (Cache["key"] == null)
  Cache["key"] = obj.processData();

return Cache["key"];

缓存本身应该确保这是安全的。

如果你希望在填充缓存时显式阻塞,那么你已经在上面的代码中有了这样做的语义,但是我推荐这个改变:

if (Cache["key"] == null) {
  Synclock blockIfProcessing
    if (Cache["key"] == null) 
       Cache["key"] = obj.processData();
  End Synclock
}

return Cache["key"];

基本上,一旦缓存很热,这会阻止您在每次通话时阻止,并且会产生更好的性能,同时保护您免受潜在竞争条件的影响。

请记住,只要你有两个不同的锁,你就可以打开潜在的死锁(这远远超出了这个线程的范围)。

寻找.Net缓存解决方案。