Иван Пузыревский — Введение в асинхронное...

30

Upload: yandex

Post on 14-Nov-2014

1.841 views

Category:

Technology


7 download

DESCRIPTION

Доклад посвящен основам асинхронного программирования. Мы кратко обсудим историю вопроса: что такое асинхронность, где, почему и зачем она используется. Затем рассмотрим наиболее частые способы построения асинхронных интерфейсов: основанные на callback'ах и на future/promise. В ходе доклада выделим основные используемые концепции, посмотрим на их реализацию и примеры использования. А в конце поговорим о сложностях, которые часто встречаются в асинхронном программировании.

TRANSCRIPT

Page 1: Иван Пузыревский — Введение в асинхронное программирование
Page 2: Иван Пузыревский — Введение в асинхронное программирование

Асинхронное программированиев C++

Пузыревский Иван, старший разработчик C++ Party, 27.03.2014

Page 3: Иван Пузыревский — Введение в асинхронное программирование

Что такое синхронность?

• Синхронное вычисление – блокирующее вызывающий поток исполнения до момента завершения работы вызываемого

• int main() { /*(1)*/ foo(42); /*(6)*/ } int foo(int x) { /*(2)*/ int y = x * x; int z = bar(y); /*(5)*/ return z;} int bar(int y) { /*(3)*/ int w = 42 + y; /*(4)*/ return w;}

Page 4: Иван Пузыревский — Введение в асинхронное программирование

Что такое асинхронность?

• Асинхронный ввод/вывод – форма ввода/вывода, допускающая продолжение вычисления до окончания передачи данных

• std::string blob(100);int rv = read(fd, blob.data(), blob.size());

• “If successful, the number of bytes actually read is returned. Upon reading end-of-file, zero is returned. Otherwise, a -1 is returned and the global variable errno is set to indicate the error. […] EAGAIN – The file was marked for non-blocking I/O, and no data were ready to be read.”

• Можно обслуживать другие сокеты во время недоступности; обрабатывать несколько запросов “одновременно”

Page 5: Иван Пузыревский — Введение в асинхронное программирование

Что такое асинхронность?

• Асинхронные события – происходящие независимо от основного потока исполнения

• RPC = Remote Procedure Call – Можно делать полезную работу после отправки запроса

• AJAX = Asynchronous Javascript and XML – Пользователь может взаимодействовать с интерфейсом во время обработки формы/клика

Page 6: Иван Пузыревский — Введение в асинхронное программирование

Что такое асинхронность?

• Асинхронное вычисление – с разделенными точками начала и завершения, причем точка завершения не связана с основным потоком исполнения

• Два основных вопроса – Как узнать о завершении вычисления? – Как возвращать значение по завершению вычисления?

Page 7: Иван Пузыревский — Введение в асинхронное программирование

Почему асинхронность?

• Две наиболее распространенных реализации: • вынесение “тяжелых” вызовов в отдельные потоки, • использование реактивной модели (epoll/kqueue)

• Во всех случаях есть фазы ожидания событий (доступность сокета на чтение/запись чтение)и реакции на события (чтение ответа/формирование запроса)

• Разница в том, кто планирует исполнение следующего фрагмента кода (ОС в случае потоков, разработчик в случае реактивной модели)

Page 8: Иван Пузыревский — Введение в асинхронное программирование

О реактивной модели

• Почему высоконагруженные системы пишут с использованием реактивной модели? – “Majority of the context-switching cost attributable to the

complexity of the scheduling decision by a modern SMP CPU scheduler” (Paul Turner, Google, at Linux Plumbers Conference 2013)

– Возможность кооперации внутри приложения – Больший контроль над планированием

Page 9: Иван Пузыревский — Введение в асинхронное программирование

О реактивной модели

• Есть два похожих, но разных понятия – конкурентность (concurrency) и параллелизм (parallelism)

• Конкурентность – композиция независимо исполняемых вычислений – как справляться с несколькими действиями одновременно?

– относится к структуре программы • Параллелизм – одновременное исполнение (возможно связанных) вычислений – как делать несколько действий одновременно? – относится к исполнению программы

Page 10: Иван Пузыревский — Введение в асинхронное программирование

Выражение асинхронности

• Как узнать о завершении вычисления? • Как возвращать значение по завершению вычисления? • Pull: дескриптор и способ его инспекции

read(fd) = EAGAIN + epoll_ctl() + epoll_wait() • Push: функция-обработчик (callback; проактивная)

// JS $.ajax({ … success: function() { console.log(“Done!”); } }) // C++ typedef void (*uv_alloc_cb)(uv_handle_t* h, size_t hint, uv_buf_t* buf); typedef void (*uv_read_cb)(uv_stream_t* s, ssize_t nread, const uv_buf_t* buf); int uv_read_start(uv_stream_t*, uv_alloc_cb alloc_cb, uv_read_cb read_cb);

• Далее доклад будет о push-модели

Page 11: Иван Пузыревский — Введение в асинхронное программирование

Выражение асинхронности

• Основные проблемы – плохое разделение зон ответственности – плохие возможности по композиции

• Мысленный эксперимент – читаем исходный запрос из сокета

(OnRequestReceived) – шлем дополнительный запрос серверу

(QueryBackend + OnBackendResponse) – формируем ответ

(OnRequestHandled) – теперь добавим дисковое кеширование в эту цепочку…

Page 12: Иван Пузыревский — Введение в асинхронное программирование

Future/Promise

• Специальный контейнер, представляющий отложенное значение – Фьюча (future) – интерфейс чтения (отложенного) значения

– Промис (promise) – интерфейс “возврата” значения • Асинхронное вычисление знает промис, который она обещает заполнить по окончании вычисления

• Пользователь значет фьючу, которая в каком-то будущем будет заполнена

Page 13: Иван Пузыревский — Введение в асинхронное программирование

Future/Promise

• template <class T> class Future<T> { bool IsSet() const; const T& Get() const; T* TryGet() const; void Subscribe(std::function<void(T)> cb); Future<R> Apply( std::function<R(T)> f); Future<R> Apply( std::function<Future<R>(T)> f);};

Page 14: Иван Пузыревский — Введение в асинхронное программирование

Future/Promise

• template <class T> class Promise<T> { bool IsSet() const; void Set(const T& value); bool TrySet(const T& value); Future<T> ToFuture() const;};

• // Создает новый (пустой) промисPromise<T> NewPromise();// Создает новую фьючу с заданным значениемFuture<T> NewFuture(const T& value);

Page 15: Иван Пузыревский — Введение в асинхронное программирование

Композиция Future

• template <class T> template <class R>Future<R> Future<T>::Apply( std::function<R(T)> f){ auto promise = NewPromise<T>(); this->Subscribe([promise] (T t) { auto result = f(t); promise.Set(result); }); return promise.ToFuture();}

Page 16: Иван Пузыревский — Введение в асинхронное программирование

Композиция Future

• template <class T> template <class R>Future<R> Future<T>::Apply( std::function<Future<R>(T)> f){ auto promise = NewPromise<R>(); this->Subscribe([promise] (T t) { auto result = f(t); result.Subscribe([promise] (R r) { promise.Set(r); }); }); return promise.ToFuture();}

Page 17: Иван Пузыревский — Введение в асинхронное программирование

Композиция Future

Page 18: Иван Пузыревский — Введение в асинхронное программирование

Примеры Future

• Future<int> value = AsyncGetValue();value.Subscribe([] (int v) { std::cerr << “Value is: “ << v << std::endl;});

• Future<int> anotherValue = value .Apply([] (int v) { return 2 * v; });

• Future<int> yetAnotherValue = value .Apply([] (int v) { return 2 * v; }) .Apply([] (int u) { return u + 1; }) .Apply([] (int w) { return w * w; });

Page 19: Иван Пузыревский — Введение в асинхронное программирование

Примеры Future

• // Вызывает cb когда вычисление закончитсяvoid DoHeavyStuff(std::function<void()> cb);

• // Возвращает фьючуFuture<void> AsyncDoHeavyStuff() { auto promise = NewPromise<void>(); DoHeavyStuff([promise] () { promise.Set(); }); return promise.ToFuture();}

Page 20: Иван Пузыревский — Введение в асинхронное программирование

О запуске вычислений

• До этого мы не касались вопросов, в каком потоке запускаются вычисления и в где исполняются обработчики

• Удобно иметь интерфейс для запуска вычислений • typedef Function<void()> Action; struct Scheduler { virtual void Schedule(Action cb) = 0; };

• Возможные реализации: – синхронный вызов – отдельный поток с очередью – поток с очередью с приоритетами – пул потоков с общей очередью

Page 21: Иван Пузыревский — Введение в асинхронное программирование

О запуске вычислений

• Часто нужно исполнить действие в конкретном потоке • Function<void()>Function<void()>::Via(Scheduler* scheduler) { return [=] () { scheduler->Schedule(*this); });}

• // OnDone будет вызван в потоке вычисленияDoHeavyStuff().Subscribe( Bind(&OnDone));// OnDone будет вызван в специальном потокеDoHeavyStuff().Subscribe( Bind(&OnDone).Via(specificThreadScheduler));

Page 22: Иван Пузыревский — Введение в асинхронное программирование

О запуске вычислений

• Часто нужно делегировать вычисление в другой поток • Function<Future<T>()>Function<T>()>::AsyncVia(Scheduler* scheduler){ return [=] () { auto promise = NewPromise<T>(); scheduler->Schedule([=] () { promise.Set(this->Run()); }); return promise.ToFuture(); };}

Page 23: Иван Пузыревский — Введение в асинхронное программирование

О запуске вычислений

• int GetNthPiDigit(int n);Bind(&GetNthPiDigit) // Function<int(int)> .AsyncVia(workerPool) // Function<Future<int>(int)> .Run(100) // Future<int> .Subscribe( Bind(&OnDone) .Via(controlThread));

Page 24: Иван Пузыревский — Введение в асинхронное программирование

О запуске вычислений

int GetNthPiDigit(int n);Bind(&GetNthPiDigit) .AsyncVia(workerPool) .Run(100) .Subscribe( Bind(&OnDone) .Via(controlThread));

Page 25: Иван Пузыревский — Введение в асинхронное программирование

Пример

• Future<string> SendByNetwork();string EncodeRequest(Request req);Response DecodeResponse(string blob);

• Future<Response> AskByNetwork(Request req) { auto blob = MakeFuture(EncodeRequest(req)); return blob .Apply(&SendByNetwork) .Apply(&DecodeResponse); }

Page 26: Иван Пузыревский — Введение в асинхронное программирование

Пример

• Future<string> SendByNetwork();string EncodeRequest(Request req);Response DecodeResponse(string blob);Scheduler* workerPool;

• Future<Response> AskByNetwork(Request req) { auto blob = Bind(&EncodeRequest).AsyncVia(workerPool).Run(req); return blob .Apply(&SendByNetwork) .Apply(Bind(&DecodeResponse).AsyncVia(workerPool));}

Page 27: Иван Пузыревский — Введение в асинхронное программирование

Пример

• Future<Request> GetRequest();Future<Payload> QueryBackend(Request req);Future<Response> HandlePayload(Payload pld);Future<void> Reply(Request req, Response rsp);

• GetRequest() .Subscribe([] (Request req) { auto rsp = QueryBackend(req) .Apply(&HandlePayload) .Apply(Bind(&Reply, req)); }

Page 28: Иван Пузыревский — Введение в асинхронное программирование

Все равно не совсем удобно

• Удобная композиция, но еще не идеально • Если проводить аналогию, то хочется исполнять

(псевдо)синхронный код в (псевдо)потоке • Сопрограммы позволяют реализовать псевдопотоки в пользовательском пространстве и сделать код псевдосинхронным

• Хочется магиюT WaitFor(Future<T> future);

• auto req = WaitFor(GetRequest());auto pld = WaitFor(QueryBackend(req));auto rsp = WaitFor(HandlePayload(pld));WaitFor(Reply(req, rsp));

• Об этом следующий доклад

Page 29: Иван Пузыревский — Введение в асинхронное программирование

старший разработчик

[email protected]

Спасибо

Пузыревский Иван

Page 30: Иван Пузыревский — Введение в асинхронное программирование

Обработка ошибок

• template <class T> class ErrorOr<T> { ErrorOr<T>(T value); ErrorOr<T>(std::exception_ptr ex); const T& GetOrThrow() const;}

• Function<ErrorOr<T>()>Function<T>()>::Guarded() { try { return ErrorOr(this->Run()); } catch (…) { return ErrorOr(std::current_exception()); }}