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_value
、set_exception
、set_value_at_thread_exit
和set_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() 是不是提供内存围栏?