在C#中装箱/取消装箱结构可以产生与原子相同的效果吗?
作者:互联网
根据C#规范,是否有任何保证foo.Bar具有相同的原子效果(即读取foo.来自不同线程的棒在被不同线程写入时永远不会看到部分更新的结构)?
我一直认为它确实如此.如果确实如此,我想知道规范是否保证它.
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的副本.这个问题要求装箱和拆箱的效果,而另一个是关于结构中的单个引用类型(不涉及装箱/拆箱).这两个问题之间唯一的相似之处是结构和原子(你真的读过这个问题吗?).
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); }
}
}
解决方法:
不,结果不是原子的.虽然对引用的更新确实是原子的,但它不是同步的.可以在装箱对象内的数据变为可见之前更新引用.
让我们把事情分开.盒装类型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);
好的,现在让我们把这个任务分开.涉及多个步骤.
>为BoxedT分配内存.
>使用值的副本初始化BoxedT.t成员.
>保存对BoxedT的引用.
步骤3的原子性意味着当您从条形图中读取时,您将获得旧值或新值,而不是两者的混合.但它不能保证同步.特别地,在操作2之前,操作3可以对其他处理器可见.
假设bar的更新对另一个处理器可见,但BoxedT.t的初始化不是.当该处理器尝试通过读取Boxed.t值来取消装箱BoxedT时,不能保证读取在步骤2中写入的t的完整值.它可能只获得值的一部分,而另一部分包含默认值( T).
这与双重检查锁定模式基本相同,但更糟糕的是因为你根本没有锁定!解决方案是使用发布语义更新bar,以便在更新bar之前将所有先前的存储提交到内存.根据C#4语言规范10.5.3节,这可以通过将bar标记为volatile来完成. (这也意味着来自bar的所有读取都将具有获取语义,这可能是您想要的,也可能不是.)
标签:c,multithreading,boxing 来源: https://codeday.me/bug/20190609/1203074.html