Reputation: 3908
EDIT: tl;dr -- this problem appears to be limited to a small set of OS/compiler/library combinations and is now tracked in the GCC Bugzilla as Bug 68921 thanks to @JonathanWakely.
I'm waiting on a future and I've noticed that top
shows 100% CPU usage and strace
shows a steady stream of futex
calls:
...
[pid 15141] futex(0x9d19a24, FUTEX_WAIT, -2147483648, {4222429828, 3077922816}) = -1 EINVAL (Invalid argument)
...
This is on Linux 4.2.0 (32-bit i686
), compiled with gcc version 5.2.1.
Here is my minimum-viable example program:
#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;
}
and here is the compiler invocation (same behavior without -g
):
g++ --std=c++11 -Wall -g -o spin-wait spin-wait.cc -pthread
Is there a more-performant alternative?
Here is a logically-similar program using std::condition_variable
that seems to perform much better:
#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;
}
Am I doing something wrong with my std::future
-based code, or is the implementation in my libstdc++
just that bad?
Upvotes: 30
Views: 1636
Reputation: 171413
No of course it shouldn't be doing that, it's a bug in the implementation, not a property of std::future
.
This is now https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68921 - the loop that keeps calling futex(2)
is in __atomic_futex_unsigned::_M_load_and_test_until
It looks like a simple missing argument to the syscall
function, so a garbage pointer is passed to the kernel, which complains that it's not a valid timespec*
argument. I'm testing the fix and will commit tomorrow, so it will be fixed in GCC 5.4
Upvotes: 12
Reputation: 3908
No, it should not. It usually works great.
(In the comments, we're trying to determine more about a specific broken configuration in which the resultant executable appears to spin-wait, but I believe that's the answer. It would still be nice to determine if this is still a spin-wait on a 32-bit target in the latest g++.)
The promise is the "push" end of the promise-future communication channel: the operation that stores a value in the shared state synchronizes-with (as defined in
std::memory_order
) the successful return from any function that is waiting on the shared state (such asstd::future::get
).
I assume this includes std::future::wait
.
[
std::promise::set_value
] Atomically stores the value into the shared state and makes the state ready. The operation behaves as thoughset_value
,set_exception
,set_value_at_thread_exit
, andset_exception_at_thread_exit
acquire a single mutex associated with the promise object while updating the promise object.
While it's a little unsettling that they describe the synchronization in terms of the promise object and not the shared-state, the intent is pretty clear.
cppreference.com[*] goes on to use it in exactly the way that wasn't working in the question above. ("This example shows how promise can be used as signals between threads.")
Upvotes: 0