这种设置障碍的方法正确吗

Is this approach of barriers right?

本文关键字:方法 设置障碍      更新时间:2023-10-16

我发现pthread_barrier_wait相当慢,所以在代码中的某个位置,我用我的barrier版本(my_barrier)替换了phread_brrier_wait,该版本使用原子变量。我发现它比pthread_barrier_wait快得多。使用这种方法有什么缺陷吗?这是正确的吗?此外,我不知道为什么它比pthread_barrier_wait更快?有线索吗?

编辑

  • 我主要感兴趣的是有相同数量的线程作为核心的情况。

    atomic<int> thread_count = 0;
    void my_barrier()
    {     
      thread_count++;
      while( thread_count % NUM_OF_THREADS )
       sched_yield();
    }
    

您的屏障实现不起作用,至少在屏障将被多次使用的情况下不会起作用。考虑这种情况:

  1. NUM_OF_THREADS-1线程正在屏障处等待,正在旋转
  2. 最后一根线到达并穿过障碍物
  3. 最后一个线程退出屏障,继续处理,完成下一个任务,然后重新进入屏障等待
  4. 直到现在,其他等待线程才得到调度,并且由于计数器再次增加,它们无法退出屏障。僵局

此外,使用动态分配的障碍来处理一个经常被忽视但令人讨厌的问题,那就是破坏/释放它们。您希望任何一个线程都能够在barrier wait返回后执行destroy/free,只要您知道没有人会再次尝试等待它,但这需要确保所有等待程序任何等待程序醒之前都已完成对barrier对象中内存的触摸-这不是一个容易解决的问题。请参阅我过去关于实施障碍的问题。。。

如何在pthread_barrier_wait返回后立即消除障碍?

能否在Linux上实现正确的故障安全进程共享屏障?

除非你知道你有一个特殊情况,没有任何困难的问题适用,否则不要尝试为应用程序实现你自己的问题。

AFAICT这是正确的,看起来更快,但在激烈竞争的情况下,情况会更糟。最激烈的情况是当你有很多线程,远远超过CPU。

不过,有一种方法可以快速设置障碍,使用事件计数(通过谷歌查看)。

struct barrier {
    atomic<int>       count;
    struct eventcount ec;
};
void my_barrier_wait(struct barrier *b)
{
    eventcount_key_t key;
    if (--b->count == 0) {
        eventcount_broadcast(&b->ec);
        return;
    }
    for (;;) {
        key = eventcount_get(&b->ec);
        if (!b->count)
            return;
        eventcount_wait(&b->ec);
    }
}

这应该能更好地扩展。

尽管坦率地说,当你使用屏障时,我认为性能并不重要,它不应该是一个需要快速的操作,它看起来很像是过早的优化。

据我所见,您的屏障应该是正确的,只要您不经常使用屏障,或者您的线程数是2的幂。理论上,你的原子会在某个地方溢出(在典型的核心计数使用了数亿次之后,但仍然如此),所以你可能想添加一些功能来重置某个地方。

现在来谈谈为什么它更快:我不完全确定,但我认为pthread_barrier_wait会让线程休眠,直到该唤醒为止。你的在这个条件下旋转,在每次迭代中都会屈服。然而,如果没有其他应用程序/线程需要处理时间,则线程可能会直接在yield之后再次被调度,因此等待时间更短。至少在我的系统中,玩这种障碍似乎表明了这一点。

附带说明:由于您使用atomic<int>,我假设您使用C++11。在这种情况下,使用std::this_thread::yield()而不是sched_yield()来消除对pthreads的依赖难道没有意义吗?

这个链接可能对您也很有帮助,它测量了各种屏障实现的性能(您的情况通常是lock xadd+while(i<NCPU),除了屈服)