代码之家  ›  专栏  ›  技术社区  ›  char m

引用分配是原子的,所以为什么需要互锁。需要交换(引用对象、对象)?

  •  94
  • char m  · 技术社区  · 15 年前

    在我的多线程ASMX Web服务中,我有一个类字段,它是我自己类型的SystemData,它由很少的 List<T> Dictionary<T> 标记为 volatile . 系统数据( _allData )偶尔刷新一次,我通过创建另一个名为 newData 用新数据填充它的数据结构。完成后,我只分配

    private static volatile SystemData _allData
    
    public static bool LoadAllSystemData()
    {
        SystemData newData = new SystemData();
        /* fill newData with up-to-date data*/
         ...
        _allData = newData.
    } 
    

    这应该可以工作,因为分配是原子的,并且引用旧数据的线程会继续使用它,其余的线程在分配之后就拥有新的系统数据。但是我的同事说不是用 不稳定的 我应该使用关键字和简单的分配 InterLocked.Exchange 因为他说在某些平台上,不能保证引用分配是原子的。另外:当我申报的时候 the _allData 字段为 不稳定的 这个

    Interlocked.Exchange<SystemData>(ref _allData, newData); 
    

    产生警告“对易失性字段的引用不会被视为易失性”我该怎么想?

    4 回复  |  直到 7 年前
        1
  •  159
  •   Eric Lippert    15 年前

    这里有许多问题。一次考虑一个:

    引用分配是原子的,所以为什么需要互锁。需要交换(引用对象、对象)?

    引用分配是原子的。互锁。交换不仅执行引用分配。它读取变量的当前值,隐藏旧值,并将新值赋给变量,所有操作都是原子操作。

    我的同事说,在某些平台上,不能保证引用分配是原子的。我的同事对吗?

    不。引用分配保证在所有.NET平台上都是原子的。

    我的同事在错误的前提下推理。这是否意味着他们的结论是错误的?

    不一定。你的同事可能会出于不好的原因给你一些好的建议。也许还有其他的原因,你应该使用interlocked.exchange。无锁编程是非常困难的,当你离开这个领域的专家们所拥护的成熟的实践时,你就会陷入困境,冒着最坏的竞争环境的危险。我既不是这个领域的专家,也不是你代码方面的专家,所以我不能以这种或那种方式做出判断。

    产生警告“对易失性字段的引用不会被视为易失性”我该怎么想?

    你应该理解为什么这是一个普遍的问题。这将导致理解为什么在这种特殊情况下警告并不重要。

    编译器发出此警告的原因是,将字段标记为volatile意味着“此字段将在多个线程上更新--不要生成缓存此字段值的任何代码,并确保此字段的任何读或写不会通过处理器缓存不一致“及时前后移动”。

    (我想你已经了解了这一切。如果您没有对volatile的含义以及它如何影响处理器缓存语义的详细了解,那么您就不了解它是如何工作的,不应该使用volatile。无锁程序很难正确执行;请确保您的程序是正确的,因为您了解它的工作原理,而不是意外地正确执行。)

    现在假设您通过向该字段传递一个引用来生成一个变量,该变量是易失性字段的别名。在被调用的方法中,编译器没有任何理由知道引用需要具有易失性语义!编译器将愉快地为未能实现易失性字段规则的方法生成代码,但是变量 不稳定的领域。这会完全破坏您的无锁逻辑;假设总是一个不稳定的字段 总是 使用易失性语义访问。有时把它当作易变的而不是其他时候,这是没有意义的;你必须 总是 保持一致,否则不能保证其他访问的一致性。

    因此,当您执行此操作时,编译器会发出警告,因为它可能会彻底破坏您精心开发的无锁逻辑。

    当然,互锁。交换 写的是期望一个不稳定的字段并做正确的事情。因此,该警告具有误导性。我对此非常遗憾;我们应该做的是实现某种机制,通过这种机制,类似interlocked.exchange的方法的作者可以在方法上放置一个属性,表示“这个采用ref的方法对变量强制执行易失性语义,因此取消警告”。也许在未来的编译器版本中,我们会这样做。

        2
  •  9
  •   Guffa    15 年前

    要么你的同事搞错了,要么他知道一些C语言规范不知道的事情。

    5.5 Atomicity of variable references 以下内容:

    “读写以下内容 数据类型是原子的:bool,char, byte、sbyte、short、ushort、uint、int、 float和引用类型。“

    因此,您可以写入易失性引用,而不会有获取损坏值的风险。

    当然,您应该注意如何决定哪个线程应该获取新的数据,以最大限度地降低一次多个线程这样做的风险。

        3
  •  6
  •   Guillaume    15 年前

    Interlocked.Exchange< T >

    将指定类型t的变量设置为指定值,并作为原子操作返回原始值。

    它会更改并返回原始值,这是无用的,因为您只想更改它,正如Guffa所说,它已经是原子的了。

    除非一个探查器被证明是应用程序中的瓶颈,否则您应该考虑解除锁定,这样更容易理解和证明您的代码是正确的。

        4
  •  2
  •   selalerer    7 年前

    Iterlocked.Exchange() 不仅是原子的,它还考虑内存可见性:

    以下同步功能使用适当的屏障来确保内存顺序:

    进入或离开关键部分的功能

    向同步对象发送信号的函数

    等待功能

    联锁功能

    Synchronization and Multiprocessor Issues

    这意味着除了原子性之外,它还确保:

    • 对于调用它的线程:
      • 没有重新排序指令(由编译器、运行时或硬件完成)。
    • 对于所有线程:
      • 在该指令看到该指令所做的更改之前,不会读取内存。
      • 此指令之后的所有读取都将看到此指令所做的更改。
      • 此指令更改到达主内存后,所有写入内存的操作都将发生(在完成时将此指令更改刷新到主内存,而不让硬件刷新它自己的ON定时)。