C# 强制执行语句的顺序

本文关键字:顺序 语句 强制执行 | 更新日期: 2023-09-27 17:56:17

我的问题是关于C#(大概是.Net)中执行保证的顺序。我举了一些我知道的Java例子来比较。

对于Java(来自"Java Concurrency in Practice")

不能保证一个线程中的操作将按照程序给出的顺序执行,只要无法从该线程中检测到重新排序 - 即使重新排序对其他线程是显而易见的。

所以代码

  y = 10;
  x = 5;
  a = b + 10;

实际上可以在分配 y = 10 之前分配 a=b+10

在Java中(来自同一本书)

当线程 A 启动由同一锁保护的同步块时,线程 A 在同步块中或之前执行的所有操作对线程 B 可见。

所以在爪哇

 y = 10;
 synchronized(lockObject) {
     x = 5;
 }
 a = b + 10;
y = 10 和 x = 5 保证在 a = b + 10 之前运行

(我不知道 y = 10 是否保证在 x = 5 之前运行)。

C# 代码对 C# 语句的执行顺序有什么保证

 y = 10;
 lock(lockObject) {
     x = 5;
 }
 a = b + 10;

我对可以提供明确参考或其他一些真正有意义的理由的答案特别感兴趣,因为像这样的保证很难测试,因为它们是关于编译器被允许做什么,而不是它每次都做什么,因为当它们失败时,当线程以错误的顺序击中事物时,您将很难重现间歇性错误。

C# 强制执行语句的顺序

ISO 23270:2006 — 信息技术—编程语言—C#, §10.10 说(我引用):

10.10 执行顺序 执行应继续进行,以便每个执行线程的副作用 保留在关键执行点。 定义副作用 作为易失性字段的读取或写入,对非易失性变量的写入, 写入外部资源,并引发异常。 这些副作用的顺序的关键执行点 应保留对易失性字段的引用(§17.4.3), lock语句 (§15.12) 以及线程创建和终止。 实现可以自由更改 C# 程序的执行顺序, 受以下约束:

  • 数据依赖性保留在执行线程中。 也就是说,计算每个变量的值,就好像所有语句一样 在线程中以原始程序顺序执行。(强调我的)。

  • 保留初始化排序规则(§17.4.4、§17.4.5)。

  • 副作用的顺序相对于易失性读数保持不变 和写作(§17.4.3)。此外,实现不需要评估 表达式,如果它可以推断出该表达式的值未被使用并且没有 产生所需的副作用(包括由调用方法或 访问易失性字段)。当程序执行被异步中断时 事件(例如另一个线程引发的异常),不能保证 可观察到的副作用在原始程序顺序中可见。

其他 CLI 标准同样可从 ISO 免费获得,网址为

  • ISO 23271:2006 — *信息技术—公共语言基础结构 (CLI) 分区 I 至 VI
  • ISO 23272-2006 — 信息技术 — 公共语言基础结构 (CLI) — 关于从分区 IV XML 文件派生信息的技术报告

但是,如果您担心多线程问题,则需要更深入地研究标准并了解有关原子性的规则。并非每个操作都保证是原子操作。如果您是多线程并调用引用除局部变量(例如,实例或类(静态)成员)以外的任何内容的方法,而不通过 lock、互斥锁、信号量或其他一些序列化技术序列化访问,那么您将面临竞争条件。

我担心你甚至会问这个,但既然你问了。

y = 10;
Thread.MemoryBarrier();
x = 5;
Thread.MemoryBarrier();
a = b + 10;
Thread.MemoryBarrier();
// ...

从 msdn

按如下方式同步内存访问:执行当前线程的处理器无法对指令进行重新排序,以便在调用 MemoryBarrier 之前执行内存访问后执行内存访问。

你要找的是 Thread.MemoryBarrier

但是,对于 Microsoft 当前的 .NET 实现,它们可能不是必需的。有关更多详细信息,请参阅此 SO 问题。

在没有阅读任何有关 .NET 内存模型的情况下,我可以向您保证 .NET 至少为您提供这些保证(即锁的行为类似于获取解锁,就像释放一样),因为它们是最弱的保证。