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