2015-09-23 14 views
4

根据C#规范,是否有任何保证foo.Bar将具有原子相同的效果(即,当由不同线程写入时,从不同线程读取foo.Bar永远不会看到部分更新的结构)?可以在C#中装箱/拆箱一个结构给它原子相同的效果?

我一直认为它的确如此。如果确实如此,我想知道规范是否能保证它。

public class Foo<T> where T : struct 
    { 
     private object bar; 

     public T Bar 
     { 
      get { return (T) bar; } 
      set { bar = value; } 
     } 
    } 

    // var foo = new Foo<Baz>(); 

编辑:@vesan这不是Atomic Assignment of Reference Sized Structs的副本。这个问题要求装箱和拆箱的效果,而另一个是关于结构中的单个引用类型(不包括装箱/取消装箱)。这两个问题之间唯一的相似之处是struct和atomic这两个词(你是否真的阅读过这个问题?)。

EDIT2:下面是基于Raymond Chen的答案原子弹版本:

public class Atomic<T> where T : struct 
{ 
    private object m_Value = default(T); 

    public T Value 
    { 
     get { return (T) m_Value; } 
     set { Thread.VolatileWrite(ref m_Value, value); } 
    } 
} 
+0

可能重复的[参考大小结构的原子分配](http://stackoverflow.com/questions/ 6992862 /原子分配的参考大小的结构) – vesan

+0

@vesan它不是重复的 - 看起来你根本不理解这个问题。 –

+1

@vesan我认为这是不正确的重复,因为这个问题是关于拆箱的结构是原子或不是,而建议的副本是关于整个结构只是参考。答案可能是同一部分规范,但具有不同的实现。 –

回答

5

没有,结果是不是原子。确实参考的更新是原子的,但它不是同步的。盒装对象内的数据变为可见之前,可以更新引用。

让我们把事情分开。盒装型T基本上是这样的:(不完全是,但足够接近本次讨论的目的)

class BoxedT 
{ 
    T t; 
    public BoxedT(T value) { t = value; } 
    public static implicit operator T(BoxedT boxed) { return boxed.t; } 
} 

当你写

bar = value; 

这是简写

bar = new BoxedT(value); 

好的,现在让我们分开作业。涉及多个步骤。

  1. 分配内存为BoxedT
  2. value的副本初始化BoxedT.t成员。
  3. bar中保存对BoxedT的引用。

步骤3的原子性意味着当您从bar中读取时,您将获得旧值或新值,而不是两者的混合。但它不保证同步。具体而言,操作3在操作2之前可能对其他处理器可见。

假设更新bar对另一个处理器可见,但BoxedT.t的初始化不是。当该处理器尝试通过读取Boxed.t值来取消装箱BoxedT时,不能保证读取在步骤2中写入的t的全部值。它可能只取值的一部分,而另一部分包含default(T)

这与双重检查的锁定模式基本相同,但更糟的是,因为根本没有锁定!解决方案是使用发布语义更新bar,以便在更新bar之前先前的所有存储均已提交到内存。根据C#4语言规范,第10.5.3节,可通过将bar标记为volatile来完成此操作。 (这也意味着从bar的所有读取将获得语义,这可能会或可能不是你想要的。)

+0

很好的答案。正是我试图找出来的! –

+0

在强大内存模式的平台上,在这种情况下将'bar'标记为'volatile'应该没有任何作用,因为商店总是按顺序提交(例如x86/x64),是正确的吗? –

+0

另外,不会'set {Thread.VolatileWrite(ref bar,value); }'诀窍,但却阻止获取'get'的语义?如果这样做不比将'bar'标记为'volatile'更好的实现? –