C# volatile double

10

由于只有引用类型和一些基元类型(包括 float,但不包括 double)可以声明为 volatile。如果我将 double 包装在一个类中,然后将其声明为 volatile(如下所示),那么 double 属性是否与任何其他 volatile 一样“读写”线程安全,还是我仍然应该考虑使用锁定?

public class MyThreadedClass
{
    volatile VolatileDouble voldub;
}

public class VolatileDouble
{
    public double Double { get; set; }
}
5个回答

18

double不能被声明为volatile的原因是它占用了64位,这使得它超过了x86上的字长大小,如果我没记错的话,这就阻止了它在CLI中被声明为volatile。

通过您目前的答案,只有引用被视为是volatile。这意味着内存模型将确保始终使用最新的引用,但它仍然可能使用旧值。

如果我是你,我会选择锁定,但另一种选择是使用Interlocked.ReadInterlocked.Exchange作用于long,再配合BitConverter.Int64BitsToDoubleBitConverter.DoubleToInt64Bits使用。您可以将其封装在VolatileDouble结构中。(我会将其制作成结构而不是类。)


有点怀疑只有引用是易变的,因此提出了这个问题。感谢确认。 - johnc
如果他确保VolatileDouble是不可变的,那么他就不应该遇到问题,对吧? - Jonathan C Dickinson
感谢您提供的Interlocked和BitConverter建议,它们激发了我全新的思路。 - johnc
@Jonathan:是的,我相信在那种情况下应该没问题——至少使用.NET 2.0内存模型,它比ECMA内存模型稍微强一些。 - Jon Skeet
很抱歉来晚了,但实际上这是C#规范固有的特性,它规定仅在大小为4或更小的类型上保证原子读/写。CIL对指针大小或更小的大小提供更强的原子读/写保证(即在x86上为4字节,在x86_64上为8字节(看着你,double))。编译器从C#编译到CIL,因此无法知道处理器将是什么。拼图的最后一块是只有具有保证的原子读/写操作的类型才能被声明为volatile。 - Ordoshsen

5
要实现上述功能,你需要使其不可变(没有setter) - 可以通过一些隐式转换操作符方便地实现。 否则,人们可以改变值而不改变(易失性的)引用。
public class MyThreadedClass
{
    volatile Box<double> volDouble = 123.45;
}

public class Box<T> where T : struct
{
    private readonly T value;
    public Box(T value) { this.value = value; }
    public T Value {get {return value;}}

    // explicit as nulls are a problem...
    public static explicit operator T(Box<T> box) {
        return box.value; }
    public static implicit operator T?(Box<T> box) {
        return box == null ? new T?() : new T?(box.value); }
    public static implicit operator Box<T>(T value) {
        return new Box<T>(value); }
    public static implicit operator Box<T>(T? value) {
        return value.HasValue ? new Box<T>(value.Value) : null; }
}

此外,锁定是最好的选择。

123.45f; 如果你没有指出来,我就没注意到 ;) - johnc

3

你只是声明了引用是易变的,而不是实例本身,所以这并不能解决问题。


怀疑是这样,因此提出问题,感谢确认。 - johnc

0

volatile 的文档有些误导...

当 MSDN 文档说它使用最新的值时,这意味着什么?我确定对于一个简单的值,这不会引起混淆,但是对于一个引用呢,正如 Brian Rasmussen 所说,你只是在谈论 ref 而不是实际的实例(因此是有趣的数据)。

从我的角度来看,使用 volatile 不是一个好主意,我会选择锁定,无论如何,这篇文章可能对你有所帮助:http://www.bluebytesoftware.com/blog/PermaLink,guid,dd3aff8a-7f8d-4de6-a2e7-d199662b68f4.aspx


-1

你不能创建一个 volatile long 或 double(64 位),但是你可以创建一个 volatile int(32 位),通过使用两个 32 位的 int,你可以成功地使用位操作来模拟 64 位(已测试并可行):

public class VolatileLong
{
    private readonly object lockObj = new object();
    private volatile int lo32;
    private volatile int hi32;
    public long Value
    {
        get
        {
            lock (lockObj)
            {
                return ((long)hi32 << 32) | ((long)lo32 & 0xFFFFFFFFL);
            }
        }
        set
        {
            lock (lockObj)
            {
                lo32 = (int)(value & 0xFFFFFFFFL);
                hi32 = (int)((value - ((long)lo32 & 0xFFFFFFFFL)) >> 32);
            }
        }
    }
}

public class VolatileDouble
{
    private readonly VolatileLong vl = new VolatileLong();
    public double Value
    {
        get
        {
            return System.BitConverter.Int64BitsToDouble(vl.Value);
        }
        set
        {
            vl.Value = System.BitConverter.DoubleToInt64Bits(value);
        }
    }
}

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