.NET 线程安全的缓存结果

6

我在C#中有类似以下的代码:

private double _x;
private bool _xCalculated;

private double GetX() {
    if (!_xCalculated) {
        _x = ... // some relatively expensive calculation
        _xCalculated = true;
    }

    return _x;
}

我的问题是,这个方法是线程安全的吗?据我所知,最坏的情况是两个或更多线程同时进入此方法并多次计算_x,但对于该类的任何实例,结果保证是相同的,因此这不是一个特别大的问题。

我的理解正确吗?

3个回答

6
一些观察:
  1. 双倍存储可能不是原子的
  2. 对布尔值的写入应该是原子的
  3. 根据CPU架构,内存操作可能会被重新排序
  4. 如果没有重新排序,代码应该可以工作
虽然我认为 x86 memory ordering guarantees 使得这种情况是安全的,但我并不完全确定。最近 .net 的内存顺序保证已经加强了(我想在 .net 4 中)以匹配 x86 的保证。

.NET中的内存模型
更多关于内存排序的内容
这意味着在.NET中,存储不会被重新排序,我认为这意味着您的代码是安全的。但无锁编程很难,所以我可能会忽略一些微妙的问题。也许if子句中的读取会引起问题。

我建议除非您是一个线程专家并且真的需要性能,否则不要使用此代码。否则,请使用更明确的锁定方式。如果没有争用,锁定并不那么昂贵。


1
为什么要猜测硬件 X 可能如何工作,而 Y 可能不行,当使用 LOCK 更简单、更清晰、更可靠,并且性能差异不值一提呢? - smirkingman
我将进行性能测试以找到最适合我的目的的最佳实现,但我想知道这个解决方案是否有效。 - derkyjadex
你的解决方案存在问题,就是很难确定它是否正确。而且由于线程问题是不确定的,你甚至无法简单地进行测试。 - CodesInChaos

2

它不是线程安全的。是的,你的理解是正确的。你可以使用lock()语句使其线程安全。

http://msdn.microsoft.com/en-us/library/c5kehkcz(VS.71).aspx

private object objLock = new object();
private double GetX() {
    lock(objLock) {
        if (!_xCalculated) { 
            _x = ... // some relatively expensive calculation 
            _xCalculated = true; 
        } 
    }
    return _x; 
} 

2
请避免使用lock(this)。最好锁定一个私有对象lockObject = new object(); 请参阅http://www.toolazy.me.uk/template.php?content=lock(this)_causes_deadlocks.xml。 - Lars Truijens
1
我认为只要按照书面顺序执行这三个步骤并且计算是无副作用的,那么代码就是安全的。即1)读取和比较标志2)写入双精度3)以原子方式写入标志。你能给出一个反例吗? - CodesInChaos
1
在读取/比较和写入标志之间,另一个线程可能会读取/比较该标志。它仍然是false,因此昂贵的计算将被执行多次。因此,它不是线程安全的。 - Hinek
1
只要计算是确定性的且没有副作用,多次计算并不成问题。使用Interlocked.CompareExchange模式进行此类重新计算很常见。 - CodesInChaos
1
我愿意接受计算可能运行多次的可能性。这并不是/那么/昂贵,对于_x的读取次数将远远超过运行代码的线程数,因此缓存结果的好处仍然大于额外的计算。 - derkyjadex
显示剩余3条评论

1

这要取决于平台,根据 .NET 的规范,我不认为这在 .NET 内存模型下是安全的,但我认为在当前的 Microsoft CLR 上应该是可以的。问题在于 CPU 可以重新排序内存写操作的程度。

请有人提供规范的详细链接吗...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接