【问题标题】:C# Is value type assignment atomic?C# 值类型赋值是原子的吗?
【发布时间】:2019-05-05 04:22:05
【问题描述】:

值类型的赋值在.Net中被认为是原子的吗?

例如,考虑以下程序:

struct Vector3
{
    public float X { get; private set; }
    public float Y { get; private set; }
    public float Z { get; private set; }


    public Vector3(float x, float y, float z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    public Vector3 Clone()
    {
        return new Vector3(X, Y, Z);
    }

    public override String ToString()
    {
        return "(" + X + "," + Y + "," + Z + ")";
    }
}

class Program
{
    private static Vector3 pos = new Vector3(0,0,0);

    private static void ReaderThread()
    {
        for (int i = 0; i < int.MaxValue; i++)
        {
            Vector3 v = pos;
            Console.WriteLine(v.ToString());
            Thread.Sleep(200);
        }

    }

    private static void WriterThread()
    {
        for (int i = 1; i < int.MaxValue; i++)
        {
            pos = new Vector3(i, i, i);
            Thread.Sleep(200);
        }
    }


    static void Main(string[] args)
    {
        Thread w = new Thread(WriterThread);
        Thread r = new Thread(ReaderThread);

        w.Start();
        r.Start();
    }
}

这样的程序会遭受高级数据竞争吗?甚至是数据竞赛

我在这里想知道的是:v 是否有可能包含:

  • 可能的数据竞争导致的垃圾值
  • 指代分配前的位置和分配后的位置的混合组件 X、Y 或 Z。例如,如果 pos = (1,1,1) 然后 pos 被赋予 (2,2,2) 的新值,可以 v = (1,2,2) 吗?

【问题讨论】:

  • 正如我所写的,我很确定它甚至没有编译bw 没有定义。也就是说,值类型复制,所以我不确定它会做什么(对你),即使它的 not 线程安全。
  • 这或多或少是伪代码,我可以将其更改为完整代码,但它实际上是否为问题添加了任何内容?
  • 并非所有的值类型赋值都是原子的。看看接受的答案:stackoverflow.com/questions/2433772/…
  • 我只是想弄清楚你在这里问的是什么。 v 的分配不会在pos 重新分配时受到影响,线程或无线程。该操作将花费复制操作所需的时间(我怀疑这是任何一种原子)
  • “但它是否真的给问题增加了任何东西”。 是的。有两种方式:1)当读者阅读你的代码时,如果有些东西很奇怪,而且你的代码是可编译的,那么它真的很奇怪。在这种情况下,您的代码无法编译,因此它可能只是随机的绒毛。 2)如果你的代码编译,我可以复制它,编译它,调试它等等。

标签: c# thread-safety atomic value-type tearing


【解决方案1】:

结构是值类型。如果将结构分配给变量/字段/方法参数,则整个结构内容将从源存储位置复制到变量/字段/方法参数的存储位置(每种情况下的存储位置都是结构本身)。

不保证复制结构是原子操作。写在C# language specification:

变量引用的原子性

以下数据类型的读写是原子的:boolcharbyte, sbyte, short, ushort, uint, int、float引用类型。在 此外,枚举类型的读取和写入具有底层类型 前面的列表也是原子的。其他类型的读写, 包括longulongdoubledecimal,以及用户自定义 类型,不保证是原子的。除了图书馆 为此目的而设计的功能,不能保证原子性 read-modify-write,比如递增或递减的情况。


所以是的,当一个线程正在一个结构存储位置复制数据时,可能会发生另一个线程出现并开始从另一个结构复制新数据 该存储位置。因此,从存储位置复制的线程最终可能会复制新旧数据的混合。


附带说明一下,由于您的一个线程如何写入变量以及该变量如何被另一个线程使用,您的代码也可能会遇到其他并发问题。 (用户 acelent 对另一个问题的回答在技术细节上很好地解释了这一点,所以我将参考它:https://stackoverflow.com/a/46695456/2819245)您可以通过封装此类“线程- lock 块中的“交叉”变量。作为lock 的替代方案,对于基本数据类型,您还可以使用Interlocked 类提供的方法以线程安全的方式访问线程交叉变量/字段(在lock 和不过,Interlocked 用于同一线程交叉变量的方法并不是一个好主意)。

【讨论】:

  • 很奇怪。有人投了反对票。我不知道为什么。我猜你不能让每个人都开心;-)
  • 感谢您的回答。不幸的是,我根本不希望使用锁,因为这会严重降低并发性。我宁愿使用一个类(通过引用传递),而是使用引用。
  • 即使使用引用类型,也必须小心。只是一个简单的例子来说明:假设您要在线程之间交换的引用类型有两个带有相关数据的属性/字段。所以,虽然一个线程正在做类似var a = vectorObject.X; var b = vectorObject.Y; 的事情(好吧,我承认,愚蠢的例子,但请幽默)。 vectorObject 是“线程交叉”变量。请记住,每个赋值可能都是原子的,但两个赋值加在一起不是。 (1/2)
  • (2/2) 在没有锁定(或其他形式的同步)的情况下,第二个线程可能会在线程一个分配 a 之后立即替换 vectorObject 中的对象引用,但没有尚未从vectorObject.Y 中提取数据以分配b。如果发生这种情况,您现在的位置就对了:第一个线程处理部分来自旧的 vectorObject 对象和部分来自新的 vectorObject 的数据(错误)混合。
  • 一个简单的解决方案是有一个专用的交换引用变量,除了将向量/数据对象引用分配给单独属于的局部变量(由单独控制的)一个特定的线程。然后,对该向量/对象的成员的任何访问都将通过其线程本地的变量发生......(因为引用类型值的读/写是原子的,您不需要显式锁定/线程同步)跨度>
猜你喜欢
  • 2011-07-26
  • 2011-01-18
  • 1970-01-01
  • 2011-10-18
  • 1970-01-01
  • 2014-05-02
  • 2011-02-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多