C++ Coroutines - Gor Nishanov - CppCon 2015
C++ Coroutines - Gor Nishanov - CppCon 2015
Melvin Conway
Joel Erdwinn
Melvin Conway
Joel Erdwinn
1 S A 1 S A
Basic Symbol Basic Symbol Basic Name
Reducer 2 C Y 2 C Y Reducer Reducer
S A Write to
3 S Y A A
Tape
S Y
output token
S A
S A read token
S A
C Y S Y read token
S A
Subroutine
C Y
Subroutine CY C
Coroutine Subroutine
Subroutine
CppCon 2015 C++ Coroutines 9/25/2015 10
100 cards per minute!
Connecting Reading
Failed Completed
atomic<long> refCnt;
mutex lock;
variant<empty, T, exception_ptr> value;
conditional_variable ready;
promise<T>
future<T> intrusive_ptr<shared_state<T>>
intrusive_ptr<shared_state<T>>
set_value(T)
wait() set_exception(exception_ptr)
T get()
.then
explicit State(int64_t total) : total(total) {}
};
auto state = make_shared<State>(total);
return Tcp::Connect("127.0.0.1", 1337).then(
[state](future<Tcp::Connection> conn) {
state->conn = std::move(conn.get());
return do_while([state]()->future<bool> {
if (state->total <= 0) return make_ready_future(false);
return state->conn.read(state->buf, sizeof(state->buf)).then(
[state](future<int> nBytesFut) {
auto nBytes = nBytesFut.get()
if (nBytes == 0) return make_ready_future(false);
state->total -= nBytes;
return make_ready_future(true);
});
}); future<void> do_while(function<future<bool>()> body) {
}); return body().then([=](future<bool> notDone) {
} return notDone.get() ? do_while(body) : make_ready_future(); });
9/25/2015 } CppCon 2015 C++ Coroutines 16
Forgot something
int tcp_reader(int total)
{
char buf[4 * 1024];
auto conn = Tcp::Connect("127.0.0.1", 1337);
for (;;)
{
auto bytesRead = conn.Read(buf, sizeof(buf));
total -= bytesRead;
if (total <= 0 || bytesRead == 0) return total;
}
}
public:
① static future<int> start(int total);
};
int main() {
cout << tcp_reader::start(1000 * 1000 * 1000).get(); }
9/25/2015 CppCon 2015 C++ Coroutines 20
Hand-crafted async state machine (2/3)
future<int> tcp_reader::start(int total) {
auto p = make_unique<tcp_reader>(total);
auto result = p->done.get_future();
Tcp::Connect("127.0.0.1", 1337,
[raw = p.get()](auto ec, auto newConn) {
raw->OnConnect(ec, std::move(newConn));
});
p.release();
return result;
}
void OnComplete() {
auto cleanMe = unique_ptr<tcp_reader>(this);
done.set_value(total);
}
Connecting Reading
Failed Completed
Visual C++ 2015 RTM. Measured on Lenovo W540 laptop. Transmitting & Receiving 1GB over loopback IP addr
9/25/2015 CppCon 2015 C++ Coroutines 26
Coroutines are closer to the metal
Handcrafted
State
Machines
Coroutines
I/O Abstractions
(Callback based) I/O Abstraction
(Awaitable based)
Hardware
OVERLAPPED aiocbp
Function Function
Object Object
?
9/25/2015 CppCon 2015 C++ Coroutines 32
Awaitable – Concept of the Future<T>
Present
T
.await_ready()
F<T> → bool
Present
T
.await_suspend(cb)
F<T> x Fn → void
Present
.await_resume()
T F<T> → T
await expr-of-awaitable-type
{
auto && tmp = <expr>;
if (!await_ready(tmp)) {
await_suspend(tmp, <coroutine-handle>);
suspend
resume
}
return await_resume(tmp);
}
me->Invoke(p.error, p.byteTransferred);
}
};
?
9/25/2015 CppCon 2015 C++ Coroutines 37
auto Connection::Read(void* buf, int len) {
struct awaiter: AwaiterBase {
Connection* me;
void* buf;
awaiter(Connection* me, void* buf, int len) : me(me), buf(buf) { bytes = len; }
void await_suspend(coroutine_handle<> h) {
this->resume = h;
auto error = me->sock.Receive(buf, bytes, this);
if (error.value() != kIoPending)
throw system_error(err);
struct AwaiterBase : os_async_context {
} coroutine_handle<> resume;
std::error_code err;
int bytes;
int await_resume() {
if (this->err) throw system_error(err); static void io_complete_callback(CompletionPacket& p){
auto me = static_cast<AwaiterBase*>(p.overlapped);
return bytes; me->err = p.error;
} me->bytes = p.byteTransferred;
me->resume();
}; }
return awaiter{ this, buf, len }; };
}
9/25/2015 CppCon 2015 C++ Coroutines 38
Trivial
auto tcp_reader(int total) -> future<int>
{
char buf[4 * 1024];
auto conn = await Tcp::Connect("127.0.0.1", 1337);
for (;;)
{
auto bytesRead = await conn.Read(buf, sizeof(buf));
total -= bytesRead;
if (total <= 0 || bytesRead == 0) return total;
}
}
SetFileCompletionNotificationModes(h,
FILE_SKIP_COMPLETION_PORT_ON_SUCCESS);
if (error.value() != kIoPending) {
o->Invoke(error, len);
return;
}
o.release();
}
}
SuperLean.exe!improved::detail::CompletionWithSizeT<<lambda_ee38b7a750c7f550b4ee1dd60c2450c1>
SuperLean.exe!improved::tcp_reader::OnRead(std::error_code ec, int bytesRead) Line 254
>::Invoke(std::error_code ec, int count) Line 31
SuperLean.exe!improved::detail::CompletionWithSizeT<<lambda_ee38b7a750c7f550b4ee1dd60c2450c1>
SuperLean.exe!improved::tcp_reader::OnRead(std::error_code ec, int bytesRead) Line 254
o.release();
SuperLean.exe!improved::detail::CompletionWithSizeT<<lambda_ee38b7a750c7f550b4ee1dd60c2450c1>
Stack
>::Invoke(std::error_code ec, int count) Line 31
conn.Read(buf, bytesRead,
[this](std::error_code ec, int bytesRead) {
OnRead(ec, bytesRead); }) ;
}
void await_suspend(coroutine_handle<> h) {
this->resume = h;
auto error = me->sock.Receive(buf, bytes, this);
if (error.value() == kIoPending) return;
if (error) throw system_error(err);
struct AwaiterBase : os_async_context {
return; coroutine_handle<> resume;
} std::error_code err;
int bytes;
bool await_suspend(coroutine_handle<> h) {
this->resume = h;
auto error = me->sock.Receive(buf, bytes, this);
if (error.value() == kIoPending) return true;
if (error) throw system_error(err);
struct AwaiterBase : os_async_context {
return false; coroutine_handle<> resume;
} std::error_code err;
int bytes;
{
auto && tmp = <expr>;
if (!await_ready(tmp)) {
await_suspend(tmp, <coroutine-handle>);
suspend
resume
}
return await_resume(tmp);
}
{
auto && tmp = <expr>;
if (!await_ready(tmp) &&
await_suspend(tmp, <coroutine-handle>) {
suspend
resume
}
return await_resume(tmp);
}
User Task
await
Generator
yield
Async
Compiler Generator
image credits: Три богатыря и змей горыныч does not care await + yield