Павел Сушин «Асинхронное программирование на С++:...

33

Transcript of Павел Сушин «Асинхронное программирование на С++:...

Асинхронное программирование на С++: callbacks, futures, fibers.

Павел Сушин, старший разработчик

Группа разработки технологий распределенных вычислений

Чем я занимаюсь

• Отказоустойчивое, консистентное, транзакционное хранилище.

• Несколько слоев хранения, с разными гарантиями на throughput и latency.

• Вычисления в модели Map-Reduce поверх этих данных.

• Серверная часть – 100% С++

• ~10 кластеров, ~2500 машин, ~25 Pb данных, сотни пользователей.

3

YT – распределенная система хранения и обработки данных.

Принцип №1

4

O_NONBLOCK

Принцип №2

5

Thread 2

Thread 3

Process state

Thread 1

Принцип №2*

6

lock-free queue

Thread

callbackcallback

SingleThreadInvoker

• Тред + очередь• Пул тредов + очередь• Тред + несколько очередей с

приоритетами • …

struct IInvoker { virtual void Invoke(callback) = 0; };

Принцип №3

7

ChunkManager Chunk

ReadChunk

Счетчики ссылок: TRefCounted + TIntrusivePtr + TWeakPtr

Callbacks

8

struct IAction { virtual void Run() = 0; };

template <class R> struct IFunc { virtual R Run() = 0; };

template <class R, class T> struct IParamFunc { virtual R Run(T t) = 0; };

template <class T> struct IParamAction { virtual void Run(T t) = 0; };

Callbacks

9

class TRequest; class TResponse; void SendMessage( TRequest req, IParamAction<TResponse> response);

class TResponseHandler : public IParamAction<TResponse> { public: TResponseHandler(TRequest req); void Run(TResponse rsp); };

... TRequest req; TResponseHandler handler(req); SendMessage(req, handler);...

10

Jpg, Png

Callbacks

11

int Sum(int a, int b) { return a + b; }

void CallAndPrint(TCallback<void(int)> f) { std::cout << f.Run() << std::endl; }

auto f = BIND(&Sum, 1); auto g = BIND(f, 2);

CallAndPrint(g);

Почему не std::function?• Видно в RefCountedTracker

• BIND - сохраняет информацию о точке связывания

• Compile-time проверки

12

class T : public TRefCounted { ... void Foo() { BIND(&T::Foo, this); // Compile-time error BIND(&T::Foo, MakeStrong(this)); BIND(&T::Foo, MakeWeak(this)); } };

Callbacks

13

class TRequest; class TResponse;

void SendRequest( TRequest req, TCallback<void(TResponse)> callback);

void ResponseHandler(TRequest req, TResponse rsp);

...TRequest req; SendRequest(req, BIND(ResponseHandler, req));

В каком потоке будет вызван ResponseHandler?

TCallback::Via

14

TCallback<void()> TCallback<void()>::Via(IInvoker* invoker) { return BIND([=]() { invoker->Invoke(*this); }); }

IInvoker* invoker;TRequest req; SendRequest( req, BIND(ResponseHandler, req).Via(invoker));

Привязываем запуск TCallback к конкретному инвокеру

Callbacks: проблемы

• Плохие возможности по композиции:

• как дождаться окончания двух асинхронных действий?

• как дважды подписаться на одно событие?

• как составлять цепочки асинхронных вычислений?

• Акцент на коллбеках, а не на результате действия.

15

16

Jpg, Png

Future/Promise

17

template <class T> class TFuture { public: bool IsSet() const; T Get() const;

void Subscribe(TCallback<void(T)> cb);

};

template <class T> class TPromise { public: TFuture<T> ToFuture() const; void Set(T t); };

TSet GetPromise Future

Future/Promise

18

Future смещает акцент с вызова коллбека на результат асинхронного вычисления.

void DoHeavyStuff(TCallback<void()> cb);

TFuture<void> AsyncDoHeavyStuff() { auto promise = NewPromise<void>(); DoHeavyStuff([promise] () { promise.Set(); }); return promise.ToFuture(); }

Future/Promise

19

class TRequest; class TResponse;

TFuture<TResponse> SendRequest(TRequest req);

void ResponseHandler(TRequest req, TResponse rsp);

... IInvoker* invoker; TRequest req; SendRequest(req).Subscribe( BIND(ResponseHandler, req) .Via(invoker));

TCallback::AsyncVia

20

Как делегировать вычисление в другой поток?

TCallback<TFuture<T>()> TCallback<T>()>::AsyncVia(IInvoker* invoker) { return BIND([=] () { auto promise = NewPromise<T>(); invoker->Invoke(BIND([=] () { promise.Set(this->Run()); })); return promise.ToFuture(); }); }

TCallback + TFuture

21

int GetNthPiDigit(int n);

BIND(&GetNthPiDigit) // TCallback<int(int)> .AsyncVia(workerPool) // TCallback<TFuture<int>(int)> .Run(100) // TFuture<int> .Subscribe( BIND(&OnDone) .Via(controlThread));

TFuture::Apply

22

template <class T> class TFuture<T> { bool IsSet() const; const T& Get() const;

void Subscribe(TCallback<void(T)> cb); TFuture<R> Apply(TCallback<R(T)> f); TFuture<R> Apply(TCallback<TFuture<R>(T)> f); };

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

TFuture::Apply

23

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

TFuture::Apply

24

TFuture<int> value = AsyncGetValue();

value.Subscribe(BIND([] (int v) { std::cerr << “Value is: “ << v << std::endl; }));

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

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

TCallback + TFuture

25

TFuture<string> SendByNetwork(string block); string EncodeRequest(TRequest req); TResponse DecodeResponse(string blob); IInvoker* workerPool;

TFuture<TResponse> SendMessage(TRequest req) { auto futureBlob = BIND(&EncodeRequest, req) .AsyncVia(workerPool) .Run();

return futureBlob .Apply(&SendByNetwork) .Apply(BIND(&DecodeResponse).AsyncVia(workerPool)); }

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

26

template <class T> class TErrorOr<T> { TErrorOr<T>(T value); TErrorOr<T>(std::exception ex); const T& GetOrThrow() const; }

TCallback<TErrorOr<T>()> TCallback<T>()>::Guarded() { try { return TErrorOr(this->Run()); } catch (…) { return TErrorOr(std::current_exception()); } }

Callbacks + Futures: проблемы

27

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

вычисления со сложной логикой перехода между стадиями?

Хочется «склеивать» асинхронные вычисления в (псевдо)синхронном стиле:

T WaitFor(TFuture<T> future);

Значит нужно самостоятельно планировать (псевдо)потоки, в которых выполняются (псевдо)синхронные вычисления.

Fibers

28

TFuture<string> SendByNetwork(string block); string EncodeRequest(TRequest req); TResponse DecodeResponse(string blob); IInvoker* workerPool;

TFuture<TResponse> SendMessage(TRequest req) { auto reqBlob = WaitFor(BIND(&EncodeRequest, req) .AsyncVia(workerPool) .Run());

auto rspBlob = WaitFor(SendByNetwork(reqBlob)); return BIND(&DecodeResponse, rspBlob) .AsyncVia(workerPool) .Run(); }

Fibers

• Контекст = состояние регистров

• Нужно уметь сохранять и переключать контекст (например использовать libcoro)

• Fiber = контекст + стек

• У треда есть очередь (готовых) файберов и собственный контекст, где он разгребает эту очередь (scheduling context)

• Текущий файбер разгребает очередь коллбеков

29

Fibers

30

• При вызове WaitFor:

• переходим в scheduling context;

• подписываем на future добавление файбера в очередь готовых;

• достаем очередной готовый файбер из очереди (или создаем новый) и ставим на исполнение

Overview

31

struct IInvoker { virtual void Invoke(TCallback<void()>) = 0; };

template <class R, class...TArgs> class TCallback { public: ... TCallback<R(...TArgs)> Via(IInvoker* invoker); TCallback<TFuture<R>(...TArgs)> AsyncVia(IInvoker* invoker); }

Overview

32

template <class T> class TFuture<T> { bool IsSet() const; const T& Get() const;

void Subscribe(TCallback<void(T)> cb); TFuture<R> Apply(TCallback<R(T)> f); TFuture<R> Apply(TCallback<TFuture<R>(T)> f); };

T WaitFor(TFuture<T> future);

Спасибо!

[email protected]

+7 916 9529490

Павел Сушин, старший разработчик

Группа разработки технологий распределенных вычислений

http://facebook.com/yandex.ev

ents

http://twitter.com/ya_events