【问题标题】:Is this approach of barriers right?这种设置障碍的方法对吗?
【发布时间】:2012-02-19 15:08:07
【问题描述】:

我发现 pthread_barrier_wait 非常慢,所以在我的代码中的一个地方,我将 pthread_barrier_wait 替换为我的屏障版本(my_barrier ),它使用原子变量。我发现它比 pthread_barrier_wait 快得多。使用这种方法有什么缺陷吗?这是正确的吗?另外,我不知道为什么它比 pthread_barrier_wait 快?有什么线索吗?

编辑

  • 我主要对线程数与内核数相等的情况感兴趣。

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

【问题讨论】:

  • 我能看到的唯一区别是 pthread_barrier_wait() 将选择一个线程通过 PTHREAD_BARRIER_SERIAL_THREAD 而所有其他线程将获得 0。如果您不需要此功能,您的实现看起来和我一样。
  • @kfmfe04:远非如此。看我的回答。实现具有 POSIX 标准所需的所有属性的屏障(其中许多属性比您想象的更有用)绝非易事。

标签: c++ c multithreading c++11


【解决方案1】:

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);
    }
}

这应该可以更好地扩展。

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

【讨论】:

  • 嗯,在我的系统中,线程数量与核心相同,因此争用不是问题。其次,如果你提到的算法真的那么快,为什么 pthread_barrier_wait 不用呢?最后,我不认为它是预优化,因为我正在使用的一些基准测试每秒有数千个障碍!
  • 当有很多线程时应该不会那么糟糕,因为等待的线程大部分时间都会休眠(由于产量),只是虚假地醒来以检查它们是否可以继续执行。所以它可能会更频繁地醒来,但我认为它不应该严重扩展(尽管可能是错误的)。
  • 事件计数并不为人所知。不过这个想法是,如果运气好的话,最后一个线程不会等待 futex 或类似的。
【解决方案2】:

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

现在为什么它更快:我不完全确定,但我认为pthread_barrier_wait 会让线程休眠,直到该醒来。你的在条件下旋转,在每次迭代中产生。但是,如果没有其他需要处理时间的应用程序/线程,则线程可能会在yield 之后直接再次调度,因此等待时间更短。至少这就是在我的系统上玩弄这种障碍的意思。

附带说明:由于您使用atomic&lt;int&gt;,我假设您使用 C++11。在这种情况下使用std::this_thread::yield() 而不是sched_yield() 来消除对pthread 的依赖不是很有意义吗?

This link 也可能对您很有吸引力,它衡量各种屏障实现的性能(您的情况大致是 lock xadd+while(i&lt;NCPU) 的情况,除了屈服)

【讨论】:

    【解决方案3】:

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

    1. NUM_OF_THREADS-1 线程在屏障处等待,旋转。
    2. 最后一个线程到达并穿过屏障。
    3. 最后一个线程退出屏障,继续处理,完成下一个任务,然后重新进入屏障等待。
    4. 现在才安排其他等待线程,它们无法退出屏障,因为计数器再次递增。死锁。

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

    How can barriers be destroyable as soon as pthread_barrier_wait returns?

    Can a correct fail-safe process-shared barrier be implemented on Linux?

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

    【讨论】:

    • 我实际上是在我的程序中发明了它,因为我有一段代码用两个 pthread_barrier_wait 进行了黑白分割,所以我用 my_barrier 替换了第一个。你认为,它对这种用途有效还是在那里无效?
    • 如果屏障只使用一次,并且永远不会被破坏/永远不会被破坏,那么您的屏障可能会起作用。但是当大量线程进入一个在屏障计数器上旋转而不是休眠的循环时,它会非常慢......
    猜你喜欢
    • 2020-04-23
    • 1970-01-01
    • 2017-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多