std::promise
and std::future
(And Some std::chrono
) (Some Live Hacking)¶
Overview¶
#include <future>
One-shot communication device
One thread promises to produce a value
Another thread waits for it to become valid in the future
std::promise<int> promise;
auto future = promise.get_future();
// producer ...
promise.set_value(42);
// consumer ...
int value = future.get();
(See below for how to transport exceptions across thread boundaries)
Overview: std::promise
¶
Operation |
Description |
---|---|
Constructor |
|
|
Move only |
|
Get |
|
Make promise come true (i.e. consumer will return from
|
|
Consumer’s |
Note
Communication is only one-shot: cannot set value (or exception) multiple times
Overview: std::future
¶
Operation |
Description |
---|---|
Constructor |
|
|
Move only |
|
Create a std::shared_future object. The original future object is invalid afterwards. |
|
If value is available, return it. Else, wait and then return. |
|
Wait for value to become available, without getting it. |
|
Timeouts of various sorts |
Utterly Wrong: Waiting For Something To Become Ready¶
Separate, uncoordinated, flag and answer
Use
nanosleep()
to poll/wait
#include <thread>
#include <iostream>
#include <cassert>
#include <time.h>
static const unsigned TEN_MILLION_YEARS_S = 2;
static const unsigned ANSWER_POLL_INTERVAL_MS = 2;
int main()
{
int answer;
bool answer_valid;
std::thread chew_answer([&answer, &answer_valid]() {
// chew on world until we know the answer
timespec ts { TEN_MILLION_YEARS_S, 0 };
int error = nanosleep(&ts, nullptr);
assert(!error);
answer = 42;
answer_valid = true;
});
while (! answer_valid) {
timespec ts { 0, ANSWER_POLL_INTERVAL_MS * 1000*1000 }; // assuming less than a second
int error = nanosleep(&ts, nullptr);
assert(!error);
}
std::cout << answer << std::endl;
chew_answer.join();
return 0;
}
Bugs
Unlocked
CPU may reorder ⟶ flag may be in place before answer is
Polling interval
Less latency ⟶ more CPU time needed
Tight loop for maximum reaction
Sideways: std::chrono
, And Literals¶
Replace
<time.h>
with<chrono>
Use cool literals with built-in units, and
constexpr
#include <chrono> using namespace std::literals::chrono_literals; static constexpr auto ANSWER_COMPUTATION_TIME = 2s; static constexpr auto ANSWER_POLL_INTERVAL = 2ms;
#include <thread>
#include <iostream>
#include <chrono>
using namespace std::literals::chrono_literals;
static const auto TEN_MILLION_YEARS = 2s;
static const auto ANSWER_POLL_INTERVAL = 2ms;
int main()
{
int answer;
bool answer_valid;
std::thread chew_answer([&answer, &answer_valid]() {
// chew on world until we know the answer
std::this_thread::sleep_for(TEN_MILLION_YEARS);
answer = 42;
answer_valid = true;
});
while (! answer_valid)
std::this_thread::sleep_for(ANSWER_POLL_INTERVAL);
std::cout << answer << std::endl;
chew_answer.join();
return 0;
}
Minimal Fix: Use std::mutex
¶
Add
std::mutex
to flag and answerDon’t (yet) use scoped locking; rather use clumsy (non-exception-safe)
lock()
andunlock()
, together with adone
flag
#include <thread>
#include <mutex>
#include <iostream>
#include <chrono>
using namespace std::literals::chrono_literals;
static const auto TEN_MILLION_YEARS = 2s;
static const auto ANSWER_POLL_INTERVAL = 2ms;
int main()
{
std::mutex lock;
int answer;
bool answer_valid;
std::thread chew_answer([&answer, &answer_valid, &lock]() {
// chew on world until we know the answer
std::this_thread::sleep_for(TEN_MILLION_YEARS);
lock.lock();
answer = 42;
answer_valid = true;
lock.unlock();
});
bool done = false;
while (! done) {
lock.lock();
if (answer_valid) {
done = true;
std::cout << answer << std::endl;
}
else
std::this_thread::sleep_for(ANSWER_POLL_INTERVAL);
lock.unlock();
}
chew_answer.join();
return 0;
}
Anti-Clumsiness: Scoped Locking¶
Use
std::scoped_lock
⟶ removedone
flag, and simply break out of loop
#include <thread>
#include <mutex>
#include <iostream>
#include <chrono>
using namespace std::literals::chrono_literals;
static const auto TEN_MILLION_YEARS = 2s;
static const auto ANSWER_POLL_INTERVAL = 2ms;
int main()
{
std::mutex lock;
int answer;
bool answer_valid;
std::thread chew_answer([&answer, &answer_valid, &lock]() {
// chew on world until we know the answer
std::this_thread::sleep_for(TEN_MILLION_YEARS);
lock.lock();
answer = 42;
answer_valid = true;
lock.unlock();
});
while (true) {
std::scoped_lock guard(lock);
if (answer_valid) {
std::cout << answer << std::endl;
break;
}
}
chew_answer.join();
return 0;
}
Pro-Readability: Encapsulate¶
Create
class Answer
Methods:
set()
,wait(duration)
#include <thread>
#include <mutex>
#include <iostream>
#include <chrono>
using namespace std::literals::chrono_literals;
static const auto TEN_MILLION_YEARS = 2s;
static const auto ANSWER_POLL_INTERVAL = 2ms;
class Answer
{
public:
Answer() : _answer_valid(false) {}
void set(int answer)
{
std::scoped_lock guard(_lock);
_answer = answer;
_answer_valid = true;
}
template <typename dur>
int wait(dur d)
{
while (true) {
std::scoped_lock guard(_lock);
if (_answer_valid)
return _answer;
std::this_thread::sleep_for(d);
}
}
private:
std::mutex _lock;
int _answer;
bool _answer_valid;
};
int main()
{
Answer answer;
std::thread chew_answer([&answer]() {
// chew on world until we know the answer
std::this_thread::sleep_for(TEN_MILLION_YEARS);
answer.set(42);
});
std::cout << answer.wait(ANSWER_POLL_INTERVAL) << std::endl;
chew_answer.join();
return 0;
}
Atomics On Structures?¶
Nested
struct data
: flag and answerUse
std::atomic<data>
insideclass Answer
#include <thread>
#include <atomic>
#include <iostream>
#include <chrono>
using namespace std::literals::chrono_literals;
static const auto TEN_MILLION_YEARS = 2s;
static const auto ANSWER_POLL_INTERVAL = 2ms;
class Answer
{
public:
void set(int answer)
{
_data = data(answer);
}
template <typename dur>
int wait(dur d)
{
while (true) {
data data = _data;
if (data.answer_valid)
return data.answer;
std::this_thread::sleep_for(d);
}
}
private:
struct data
{
data() noexcept : answer_valid(false), answer(0) {}
data(int answer) noexcept : answer_valid(true), answer(answer) {}
bool answer_valid;
int answer;
};
std::atomic<data> _data;
};
int main()
{
Answer answer;
std::thread chew_answer([&answer]() {
// chew on world until we know the answer
std::this_thread::sleep_for(TEN_MILLION_YEARS);
answer.set(42);
});
std::cout << answer.wait(ANSWER_POLL_INTERVAL) << std::endl;
chew_answer.join();
return 0;
}
Anti-Polling: Semaphore¶
No
std::mutex
⟶std::binary_semaphore
(initialized with 0)Discuss barrier instruction (signal/wait semaphore)
Discuss:
_answer_valid
not needed when properly waitedDiscuss: OS wait conditions, blocking system calls, realtime
#if __cplusplus >= 202001L
#include <thread>
#include <semaphore>
#include <iostream>
#include <cassert>
#include <chrono>
using namespace std::literals::chrono_literals;
static constexpr auto TEN_MILLION_YEARS = 2s;
class Answer
{
public:
Answer() : _notifier{0}
{}
void set(int answer)
{
_answer = answer;
_notifier.release();
}
int wait()
{
_notifier.acquire();
return _answer;
}
private:
std::binary_semaphore _notifier;
int _answer;
};
int main()
{
Answer answer;
std::thread chew_answer([&answer]() {
// chew on world until we know the answer
std::this_thread::sleep_for(TEN_MILLION_YEARS);
answer.set(42);
});
std::cout << answer.wait() << std::endl;
chew_answer.join();
return 0;
}
#else
#include <iostream>
using namespace std;
int main()
{
cerr << "no!" << endl;
return 0;
}
#endif
Getting To The Point: std::promise
And std::future
¶
#include <thread>
#include <future>
#include <iostream>
#include <chrono>
using namespace std::literals::chrono_literals;
static const auto TEN_MILLION_YEARS = 2s;
int main()
{
std::promise<int> answer_promise;
auto answer_future = answer_promise.get_future();
std::thread chew_answer([&answer_promise]() {
// chew on world until we know the answer
std::this_thread::sleep_for(TEN_MILLION_YEARS);
answer_promise.set_value(42);
});
std::cout << answer_future.get() << std::endl;
chew_answer.join();
return 0;
}
And Exceptions?¶
#include <thread>
#include <future>
#include <iostream>
#include <chrono>
#include <exception>
using namespace std::literals::chrono_literals;
static const auto TEN_MILLION_YEARS = 2s;
int main()
{
std::promise<int> answer_promise;
auto answer_future = answer_promise.get_future();
std::thread chew_answer([&answer_promise]() {
// chew on world until we know the answer
std::this_thread::sleep_for(TEN_MILLION_YEARS);
answer_promise.set_exception(std::make_exception_ptr(std::runtime_error("bummer!")));
});
try {
answer_future.get();
}
catch (const std::runtime_error& e) {
std::cerr << e.what() << std::endl;
}
chew_answer.join();
return 0;
}