进入Windows临界区是原子操作吗?

Is Entrance into a Windows Critical Section an atomic operation?

我为关键部分写了一个 FFI,我在 Haxe 中为它写了一个测试。

测试 运行 按定义的顺序(public function 是测试)

此测试 test_critical_section 会间歇性挂起并失败:

1   var criticalSection:CriticalSection;
2
3   #if master
4   public function test_init_critical_section() {
5       return assert(attempt({
6           criticalSection = synch.SynchLib.critical_section_init(SPIN_COUNT);
7           trace('criticalSection: $criticalSection');
8       }));
9   }
10  var criticalValue = 0;
11  var done = 0;
12  var numThreads = 50;
13  function work_in_critical_section(ID:Int, a:AssertionBuffer) {
14      sys.thread.Thread.create(() -> {
15          inline function threadMsg(msg:String)
16              trace('Thread ID $ID: $msg');
17          
18          
19          threadMsg("Attempting to enter critical section");
20          criticalSection.critical_section_enter();
21          threadMsg("Entering crtiical section. Doing work.");
22          Sys.sleep(Std.random(100)/500); // simulate work in section
23          criticalValue+= 10;
24          done++;
25          a.assert(criticalValue == done * 10);
26          threadMsg("Leaving critical section. Work done. done: " + done);
27          criticalSection.critical_section_leave();
28          if (done == numThreads) {
29              a.assert(criticalValue == numThreads * 10);
30              a.done();
31              
32          }
33      });
34  }
35  @:timeout(30000)
36  public function test_critical_section() {
37      var a = new AssertionBuffer();
38      for (i in 0...numThreads)
39          work_in_critical_section(i, a);
40      return a;
41  }

但是当我在进入临界区之前添加 Sys.sleep(ID/5); 时(在空白行 18 上),测试每次都通过(使用任意数量的线程)。没有它,测试会随机失败(更多的线程数更多)。

我从这个测试中得出的结论是,进入临界区不是原子的,多个线程同时尝试进入可能会使临界区处于未定义状态(导致 undefined/hanging 行为)。

这是正确的结论还是我只是误用了关键部分(因此需要重写测试)?如果这是正确的结论.. 这是否意味着进入临界区需要它自己的原子 locking/synchronization 机制..? (而且,如果是这样的话……临界区的意义何在,我为什么不直接使用原子同步机制呢?)

对我来说,这似乎是有问题的,例如,考虑 10 个线程在同步障碍处相遇(容量为 10),然后所有 10 个线程都需要在第 10 个线程到达后立即通过临界区,是否这意味着我必须 synchronize/serialize 访问关键部分入口方法(例如,通过睡眠以确保只有一个线程尝试在给定的滴答时进入该部分,就像修复上面失败的测试一样)?

FFI 写在 synchapi.h 之上(参见 EnterCriticalSection

您阅读 done 超出了临界区。那是一种竞争条件。如果要查看 done 的值,则需要在 leave 临界区之前查看。

您可能会看到从另一个线程写入 done,在写入 criticalValue 之前触发 assert 对于看到写入 [=10= 的线程可见].

如果临界区保护 criticalValuedone,那么在不进入临界区的情况下访问它们中的任何一个都是错误的,除非您确定可能访问它们的每个线程都已终止.您的代码违反了这条规则。