局部变量是否需要同步?

Unk*_*own 1 c# variables thread-synchronization

当多个线程同时访问数据时,必须同步访问以防止数据完整性问题.局部变量是否需要同步?

Eri*_*ert 5

当多个线程同时访问数据时,必须同步访问以防止数据完整性问题.局部变量是否需要同步?

问题表明你以错误的方式思考问题.一旦你开始正确思考问题,你就不需要再问这个问题了.

让我们首先删除问题中的错误.

当多个线程同时访问数据时,必须同步访问以防止数据完整性问题.

这是一种常见的特征,但它实际上是不正确的,因为它太弱了.描述情况的正确方法是:

  • 可以观察到多个线程上的存储的读取和写入以不同于每行代码的顺序执行所暗示的顺序的顺序发生.
  • 不要求任何线程都遵守全局一致的排序.两个线程可能不同意读取是在写入之前还是之后.
  • 由于可以观察到读取和写入在不同线程上以不同顺序发生,因此事情同时发生的整个想法是无用的.
  • C#规范限制了某些类型的读写相对于彼此的有效重新排序,以及某些其他"特殊事件",例如锁定,结束线程,抛出异常等等.有关详细信息,请参阅C#规范.

所以它不是:

当多个线程同时访问数据时,必须同步访问以防止数据完整性问题.

在多线程代码的疯狂世界中,可能会出现与同时访问或数据完整性无关的问题.相反,它会更准确地说:

当多个线程对内存执行读写操作时,可以观察到这些读写操作是意外顺序,并且在意外时间违反程序不变量,除非使用同步原语将有效重新排序限制为保留程序不变量的顺序.

现在你的问题的答案是明确的:

局部变量是否需要同步?

如果必须遵守特定事件的特定顺序对局部变量进行读写操作以保留程序不变量,并且C#规范尚未保证所需的顺序,那么就像所有内存中的读写一样,他们必须使用同步原语来实现所需的顺序.

不要陷入其他一些答案所说的"当地人在堆栈中"的陷阱 - 他们不一定是短期游泳池!仅本地生命周期短于方法激活时,并且当方法激活逻辑上形成堆栈时,本地仅在短期池中分配.作为lambda的闭合本地的本地具有较长的生命周期,迭代器块或异步方法中的本地是在其激活不形成堆栈的方法中.

此外,谁在乎呢?堆栈内存可以使用不安全的代码在线程之间共享.

也不要陷入认为某些当地人是"真正的当地人"而有些人是"假的本地人"的陷阱.本地的定义特征是它的名称具有局部范围,而不是它的寿命短.不保证当地人在C#中的寿命很短.

以下是一些可以在多个线程上访问本地的方法:

  • 本地位于迭代器块中; 在IEnumerator可以在多个线程访问.请注意,这几乎肯定会死得很厉害; 在这些条件下,迭代器块的设计并不安全.
  • 本地处于异步方法中,该方法在等待的线程以外的线程上恢复.虽然这不涉及"同时"访问,谁在乎?根本问题不在于是否可以同时访问本地,而是观察到本地的读取和写入是否与特殊事件有关.
  • 本地是一个封闭的本地lambda或匿名方法; 可以在多个线程上调用委托(或编译的表达式树).谨慎使用!使用任务并行库很容易.
  • 本地是一种非管理型; 指向本地的指针被传递给多个线程,现在可以读取或写入它. 即使本地"在堆栈中"也可能发生这种情况.当然,当您使用不安全的代码时,安全系统会关闭,您有责任确保安全.这就是为什么它被称为"不安全".