C# 强制执行语句执行的顺序

2022-09-04 02:56:20

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

对于 Java(来自“Java Concurrency in Practice”)

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

所以代码

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

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

在Java中(来自同一本书)

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

所以在Java中

 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;

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


答案 1

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

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

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

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

  • 副作用的顺序在易失性读取和写入方面保持不变(§17.4.3)。此外,如果实现可以推断出表达式的值未被使用,并且没有产生任何必需的副作用(包括由调用方法或访问易失性字段引起的任何副作用),则无需计算表达式的一部分。当程序执行被异步事件(如另一个线程引发的异常)中断时,不能保证可观察到的副作用在原始程序顺序中可见。

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

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


答案 2

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

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

来自 msdn

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