std::future::wait 应该使用这么多 CPU 吗?有没有更高效的调用?

Posted

技术标签:

【中文标题】std::future::wait 应该使用这么多 CPU 吗?有没有更高效的调用?【英文标题】:Should std::future::wait be using so much CPU? Is there a more performant call? 【发布时间】:2015-12-14 03:43:18 【问题描述】:

编辑:tl;dr -- 这个问题似乎仅限于一小部分操作系统/编译器/库组合,现在在 GCC Bugzilla 中被跟踪为Bug 68921,感谢@JonathanWakely。强>

我正在等待未来,我注意到top 显示 100% 的 CPU 使用率,strace 显示稳定的futex 调用流:

...
[pid 15141] futex(0x9d19a24, FUTEX_WAIT, -2147483648, 4222429828, 3077922816) = -1 EINVAL (Invalid argument)
...

这是在 Linux 4.2.0(32 位 i686)上,使用 gcc 版本 5.2.1 编译的。

这是我的最小可行示例程序:

#include <future>
#include <iostream>
#include <thread>
#include <unistd.h>

int main() 
  std::promise<void> p;
  auto f = p.get_future();

  std::thread t([&p]()
    std::cout << "Biding my time in a thread.\n";
    sleep(10);
    p.set_value();
  );

  std::cout << "Waiting.\n";
  f.wait();
  std::cout << "Done.\n";

  t.join();
  return 0;

这里是编译器调用(没有-g的行为相同):

g++ --std=c++11 -Wall -g -o spin-wait spin-wait.cc -pthread

有没有更好的替代方案?

这是一个使用std::condition_variable 的逻辑相似的程序,它的性能似乎要好得多

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#include <unistd.h>

int main() 
  bool done = 0;
  std::mutex m;
  std::condition_variable cv;

  std::thread t([&m, &cv, &done]()
    std::cout << "Biding my time in a thread.\n";
    sleep(10);
    
      std::lock_guard<std::mutex> lock(m);
      done = 1;
    
    cv.notify_all();
  );

  std::cout << "Waiting.\n";
  
    std::unique_lock<std::mutex> lock(m);
    cv.wait(lock, [&done] return done; );
  
  std::cout << "Done.\n";

  t.join();
  return 0;

我的基于std::future 的代码有问题吗,还是我的libstdc++ 中的实现有那么糟糕?

【问题讨论】:

也许你应该找出为什么 futex 被一个无效的参数调用,并修复它... 我敢打赌,在某个地方有类似 while(!future_is_done) futex(...); 的代码,他们希望对 futex 的调用等待一段时间,但如果失败,它最终会意外成为无限循环。 我想让互联网有机会告诉我我的代码有多么错误,然后才假设它是别人的错误,但是是的,感觉libstdc++ 要么有错误,要么有一些错误某种未说明的假设。 linux 4.2.0 (ubuntu 15.10), g++5.2.1, 64-bit, 这里没有明显的 cpu 使用率。 @RobStarling 确实,-m32 我可以重现您所描述的问题:100 % cpu 使用率和大量 futex 等待。 【参考方案1】:

不,当然不应该这样做,这是实现中的错误,而不是 std::future 的属性。

现在是https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68921 - 不断调用futex(2) 的循环在__atomic_futex_unsigned::_M_load_and_test_until

它看起来像syscall 函数的一个简单的缺失参数,因此一个垃圾指针被传递给内核,它抱怨它不是一个有效的timespec* 参数。我正在测试修复,明天将提交,所以它将在 GCC 5.4 中修复

【讨论】:

@TemplateRex,这似乎与此答案没有直接相关的评论。请参阅 gcc.gnu.org/develop.html,它仅解释文档和回归修复。可以对严重的非回归错误或不构成破坏发布分支风险的琐碎修复(但不仅仅是因为有人 ping 它)进行例外处理。固定在主干上的东西不会自动固定在发布分支上,这完全符合政策,所以你不应该对此感到惊讶。 gcc 5 的功能冻结是 2014 年 11 月!这个修复看起来很简单,但我不知道它是否安全。【参考方案2】:

不,不应该。它通常效果很好。

(在 cmets 中,我们正在尝试确定更多关于特定损坏配置的信息,其中生成的可执行文件似乎在旋转等待,但我相信这就是答案。确定这是否仍然是一个很好的选择在最新的 g++ 中旋转等待 32 位目标。)

promise 是 promise-future 通信通道的“推送”端:在共享状态中存储值的操作同步(如 std::memory_order 中定义的)任何函数的成功返回等待共享状态(例如std::future::get)。

我假设这包括std::future::wait

[std::promise::set_value] 以原子方式将值存储到共享状态并准备好状态。 该操作的行为就像 set_valueset_exceptionset_value_at_thread_exitset_exception_at_thread_exit 在更新承诺对象时获取与承诺对象关联的单个互斥体。

虽然他们用承诺对象而不是共享状态来描述同步有点令人不安,但意图很明确。

cppreference.com[*] 继续以上述问题中不起作用的方式使用它。 (“这个例子展示了如何将 Promise 用作线程之间的信号。”)

【讨论】:

我觉得这并不令人不安,因为共享状态在调用时与该承诺相关联,并且共享状态一次只能与一个承诺相关联,而互斥锁只有对 promise 的操作才需要,而不是等待相同共享状态的读者需要。 知道了。 mutex 注释意味着只有一件事可以与 promise 交互以使状态准备就绪,而 synchronizes-with 注释意味着任何等待状态的东西都能看到该值。 是的,该互斥体(它不必实际存在,只要实现表现得像它一样)用于序列化对 set_xxx 函数的调用。共享状态上的大多数操作都需要是原子的 w.r.t 读取器和写入器,但这是对(或至少“通过”)promise 的操作。

以上是关于std::future::wait 应该使用这么多 CPU 吗?有没有更高效的调用?的主要内容,如果未能解决你的问题,请参考以下文章

std::promise::set_value() 和 std::future::wait() 是不是提供内存围栏?

异常传播和 std::future

消息中间件这么多,到底应该如何选型?

系统学习 Java IO (十六)----这么多类,应该用哪个?

为啥每次递归都使用这么多的堆栈空间?

分布式事务方案这么多,到底应该如何选型?