进入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= 的线程可见].
如果临界区保护 criticalValue
和 done
,那么在不进入临界区的情况下访问它们中的任何一个都是错误的,除非您确定可能访问它们的每个线程都已终止.您的代码违反了这条规则。
我为关键部分写了一个 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= 的线程可见].
如果临界区保护 criticalValue
和 done
,那么在不进入临界区的情况下访问它们中的任何一个都是错误的,除非您确定可能访问它们的每个线程都已终止.您的代码违反了这条规则。