EnterSynchronizationBarrier在Windows 8中挂起

时间:2016-05-22 18:46:11

标签: c++ multithreading winapi windows-8

我尝试将新API用于Windows 8的同步障碍,但以下简单代码有时会在Windows 8中挂起:

#undef WINVER
#define WINVER 0x0603
#include "windows.h"
#include <thread>
#include <vector>

int main()
{
  SYNCHRONIZATION_BARRIER barrier;
  int count = 32;
  InitializeSynchronizationBarrier (&barrier, count, -1);
  std::vector<std::thread> threads;

  for (int thr_num = 0; thr_num < count; thr_num++)
  {
    threads.emplace_back ([thr_num]
    {
      for (int i = 0; i < 100000; i++)
        EnterSynchronizationBarrier (&barrier, 0);
    });
  }

  for (auto &thr : threads)
    thr.join ();

  return 0;
}

在32核双Xeon E5 2630上的Windows 8.1 64位上进行了测试。在10次发布中,它大约挂起一次。 似乎在Windows 10中它正常工作(在另一台机器上)。这是Windows 8中修复的错误,或者这不是EnterSynchronizationBarrier的正确用法(也许你不能在循环中调用它?)。关于这个功能的信息不多,有人甚至使用过吗?

1 个答案:

答案 0 :(得分:1)

这并不重要,除非是为了表明某些问题过于模糊,以至于 Stack Overflow 无法在有用的时间给予密切关注,但是您的使用是正确的,如果极端的话,以及您对被调用函数施加的压力看起来确实暴露了它的内存障碍问题。

在您的片段中,为 32 个线程准备了一个同步屏障,您创建了 32 个线程,每个线程进行 100000 个同步工作阶段。所有 32 人都将他们的电话号码 N 到达 EnterSynchronizationBarrier,然后才在前往他们的电话号码 N+1 的途中被释放。它应该工作。如果您的阶段有任何实质内容,则可能会如此。

强调的是,调用之间的每个阶段只是在循环回重复调用中涉及的指令很少。当结束阶段 N 的最后一个线程在调用中时,它会通知其他线程离开,并且他们很有可能离开(甚至重新进入函数以结束它们的阶段 N+1),而结束阶段 N 的线程仍在进行内部簿记。

在这个簿记中有两个计数器。一个,根据微软的符号文件命名为 Barrier,随着线程进入同步屏障而递减。另一个名为 LeftBarrier,在离开时递增。结束阶段的线程从 LeftBarrier(应该是所有参与线程的计数)重置 Barrier 并将 LeftBarrier 重置为 1。或者它是一个简化。

复杂的现实是屏障计数过载:其高位表示相位变化。如果在同步屏障处等待的线程正在自旋而不是在事件上阻塞,那么它在自旋时检查的是 Barrier 中的高位是否已更改。因此,在结束线程的簿记中如何重置计数器确实很重要。顺序是:读取LeftBarrier;将 LeftBarrier 写为 1;将 Barrier 写成旧的 LeftBarrier,高位切换。

我认为如果没有内存屏障,可以在 LeftBarrier 之前写入 Barrier 计数,但是因为 Barrier 有一个切换的高位,一个旋转线程从它的旋转中出来并在第一次重置之前从另一个处理器增加 LeftBarrier它为 1。增量丢失,此后所有赌注都将关闭,因为后续阶段会发现阶段结束时的 LeftBarrier 不再是参与线程的计数。

Windows 8 和 8.1 在这里没有内存障碍。 Windows 10 确实如此,但我认为它在错误的位置并且 Windows Vista 和 Windows 7 在两次写入之间正确地使用了它。无论如何,版本 1607 的实现已经完全重新设计,以便它现在使用 WaitOnAddress 功能,就像后来 Raymond Chen 的博客所描绘的那样,而不是您的一位通讯员引用的那个。在引用这篇博客时,Microsoft(虽然可能不是 Raymond)肯定知道该函数的两个早期代码更改与内存屏障相关。