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

.net 线程安全一次性计算最佳实践,.net,multithreading,caching,locking,.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 )

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

通常我使用一个锁和一个布尔顶部检查,如果它被处理与否。有时我在访问器中这样做

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

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

   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
编辑:


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

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

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

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

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

这将避免第一次初始化后的关键部分。

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

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

这将避免在第一次初始化后出现临界段。

临界段是否从不计算两次?i、 e.如果两个线程同时请求,并独立计算值,这是一个显示停止符吗?在大多数情况下,情况并非如此——在这种情况下,只需检查
null
(因为它是一个字符串):(C#中的示例,抱歉):

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

这具有无锁和简单的优点

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

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

这是延迟计算的,但静态初始值设定项的规则意味着它保证只运行一次(不包括反射)。

它从不计算两次是否至关重要?i、 e.如果两个线程同时请求,并独立计算值,这是一个显示停止符吗?在大多数情况下,情况并非如此——在这种情况下,只需检查
null
(因为它是一个字符串):(C#中的示例,抱歉):

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

这具有无锁和简单的优点

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

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

这是延迟计算的,但静态初始值设定项的规则意味着它保证只运行一次(不包括反射)。

首先,我会将缓存移到包含业务逻辑的类之外,保持业务逻辑的纯净,并允许您独立于应用程序控制缓存。但这不是你的问题

在缓存变热之前,您不必提及是否会多次计算这些内容。简单的方法是:

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缓存解决方案。

首先,我会将缓存移到包含业务逻辑的类之外,保持业务逻辑的纯净,并允许您独立于应用程序控制缓存。但这不是你的问题

在缓存变热之前,您不必提及是否会多次计算这些内容。简单的方法是:

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缓存解决方案。

事实上,这是一个很好的观点,在最坏的情况下,它将被计算3次。我肯定不止这些。(请参阅更新,了解一种不使用锁就永远不会重新计算的方法)@Marc即使没有锁,我仍然认为内部存在锁,对吗?我真的不知道它是如何在幕后实现的-尽管类似。这实际上是一个很好的观点,在最坏的情况下,将计算3次。我肯定不止这些。(请参阅更新,了解一种不使用锁就永远不会重新计算的方法)@Marc即使没有锁,我仍然认为内部存在锁,对吗?我真的不知道它是如何在幕后实现的-尽管类似。理论上,这可能返回null(另一个线程可能会在分配项目和返回项目之间从缓存中删除该项目。为了避免这种情况,请使用temp=cache[“key”];if(temp==null){temp=processData();cache[“key”=temp;}return temp;理论上这可能会返回null(另一个线程可能会在分配项和返回项之间从缓存中删除该项。要避免这种情况,请使用temp=cache[“key”];if(temp==null){temp=processData();cache[“key”=temp;}return temp;