Дмитрий Жестилевский — Yet another threading framework: асинхронная...

Post on 15-Jan-2015

516 views 4 download

Tags:

description

Код приложений для мобильных устройств, написанный на C++, часто оказывается сложнее аналогичного на Java, ObjC или C#. Данные языки предлагают решения стандартных задач разработчика «из коробки». Многие из этих задач связаны с асинхронным выполнением операций. В докладе я представлю подход к написанию понятного и производительного асинхронного кода на С++, который применяется в разработке библиотек для мобильных геоприложений в Яндексе.

transcript

1

2

карты

Асинхронная разработка на C++

Дмитрий Жестилевский

3

Содержание

!  Как работает загрузка карты ! Стейт-машина и линейный код

!  С++11 concurrency !  Наш вариант concurrency

!  Пример реализации загрузки карты

4

5

Тайл

!  Можно отменять !  Должен кешироваться !  Должен обновляться !  Один тайл может содержать множество версий

6

Схема загрузки одного тайла Старт

Обновился?

Есть в памяти?

Есть на диске?

Сходить в диск

Сходить в сеть

Конец

Вернуть тайл

Декодировать и положить в память

Вернуть тайл

Вернуть тайл

Декодировать и положить в память

Нет

Да

Да

Да

Нет

Нет

7

Конечный автомат Он же стейт-машина

8

Row<NotInRam , RequestTile , LoadingFromDisk , none , none >, !Row<NotInRam , DiscardTile , none , none , none >, !Row<NotInRam , UpdateTile , none , none , none >, !// +-------------+--------------------------+-----------------+-------------------+-----------------------+ !Row<LoadingFromDisk, RequestTile , none , none , none >, !// this transition will be performed if isTileUpToDate returns false !Row<LoadingFromDisk, TileReady , UpdatingFromNet , NotifyCaller , none >, !Row<LoadingFromDisk, TileReady , UpToDateInRam , NotifyCaller , IsTileUpToDate >, !Row<LoadingFromDisk, TileRequestError , LoadingFromNet , none , none >, !// +-------------+--------------------------+-----------------+-------------------+-----------------------+ !Row<LoadingFromNet , RequestTile , none , none , none >, !Row<LoadingFromNet , TileReady , InRamSyncing , NotifyCaller , none >, !Row<LoadingFromNet , TileRequestError , NotInRam , NotifyCaller , none >, !// +-------------+--------------------------+-----------------+-------------------+-----------------------+ !Row<UpdatingFromNet, RequestTile , none , NotifyCaller , none >, !Row<UpdatingFromNet, TileReady , InRamSyncing , NotifyCaller , none >, !Row<UpdatingFromNet, TileRequestError , OldInRam , none , none >, !// +-------------+--------------------------+-----------------+-------------------+-----------------------+ !Row<InRamSyncing , RequestTile , none , NotifyCaller , none >, !Row<InRamSyncing , DiscardTile , none , none , none >, !Row<InRamSyncing , TileSyncingCompleted , UpToDateInRam , none , none >, !Row<InRamSyncing , TileSyncingError , UpToDateInRam , none , none >, !// +-------------+--------------------------+-----------------+-------------------+-----------------------+ !// May be we don't need this state. !Row<OldInRam , RequestTile , UpdatingFromNet , NotifyCaller , none >, !Row<OldInRam , UpdateTile , UpdatingFromNet , none , none >, !Row<OldInRam , DiscardTile , none , none , none >, !// +-------------+--------------------------+-----------------+-------------------+-----------------------+ !Row<UpToDateInRam , RequestTile , none , NotifyCaller , none >, !Row<UpToDateInRam , UpdateTile , none , none , none >, !Row<UpToDateInRam , UpdateTile , UpdatingFromNet , none , Not_<IsTileUpToDate> >, !Row<UpToDateInRam , DiscardTile , none , none , none >, !// +-------------+--------------------------+-----------------+-------------------+-----------------------+ !Row<AllOk , DiscardTile , CancelledMode , none , none >, !Row<CancelledMode , RequestTile , AllOk , none , none >!

9

Линейный код

void loadTile(int x, int y, output_iterator output) { ! if (auto tile = inMemory(x, y)) { ! output << tile; ! } !! if (auto tile = readFromDisk(x, y)) { ! output << decode(tile); ! prevVersion = tile.version; ! } !! while (true) { ! Tile tile = readFromNet(x, y, prevVersion); ! if (tile.version == prevVersion) continue; ! prevVersion = tile.version; ! output << decode(tile); ! } !} ! !

10

C++11 concurrency

11

Future/Promise

Future Promise

Promise<int> p; !Future<int> f = p.future(); !

promise.set(val); !std::cout << value; !

auto future = startAsync(); !// ... !// ... !int value = future.get(); !

Promise<int> promise; !

int val = calc(); !

12

Использование Future

int meaningOfLife() { ! sleep(100500); ! return 42; !} !!int main() { ! std::future<int> meaningOfLife = std::async([] { ! return meaningOfLife(); ! }); !! std::future<int> calculation = std::async([] { return calcSmth(); }); !! std::cout << meaningOfLife().get() + calculation.get() << std::endl; !} !

{  calcSmth Meaning

Of Life

}  

output  

13

std::async

Пример реализации std::async в случае создания отдельного потока на каждую операцию

Future<T> async(Function<T()> func) { ! Promise<T> promise; ! auto future = promise.future(); !! std::thread([promise = std::move(promise), func] { ! try { ! promise.setValue(func()); ! } catch (...) { ! promise.setException(std::current_exception()); ! } ! }).detach(); !! return future; !} !

14

IO binding

Использование асинхронной сети в синхронном коде

void NetworkingAPI::httpGet(std::string url, ! std::function<void(Response, Error)>);!!Future<Response> httpGet(const std::string& url) { ! Promise<Response> promise; ! auto future = promise.future(); !! NetworkingAPI::httpGet(url, ! [promise = std::move(promise)] ! (Response response, Error error) { ! if (error) ! promise.setException(error); ! else ! promise.setValue(response); ! }); !} !

15

Чего нет в std::

!  Масштабируемость ! Отменяемость асинхронных операций

!  Генераторы — функции, возвращающие множество результатов

16

Контексты исполнения

!  Потоки !  Процессы

17

Coroutine – user-space thread Пишем асинхронный код синхронно

18

Примитивы

async::Future !async::Promise !

async::Mutex!async::CV !

Coroutines!Coro Scheduler !

std::future !std::promise !

std::mutex!std::cv !

Threads !OS Scheduler !

Future.get() не блокирует поток

Реализованы ~одинаково

19

Модель потоков

Все взаимодействие с IO – в отдельных выделенных потоках (сеть, диск) Все остальное – в глобальном thread pool

20

Отмена операций

!  Отмена через exception (Future.reset, ~Future) !  Cancellation points: wait, sleep, get

!  Мгновенная отмена или ленивая?

21

Генераторы

22

Поточная десериализация Генератор1: байты Генератор2: байты объекты

MultiFuture<char> networkBytes(); !!MultiFuture<Object> objects(MultiFuture<char> bytesStream) { ! return async<Object>([](MultiPromise<Object>* output) { ! Parser parser(std::move(bytesStream)); !! while (!parser.finished()) { ! output->yield(parser.next()); ! } ! }; !} !!void Parser::read(int size, char* data) { ! while (size > 0) { ! *data = bytes_.get(); ! data++; ! size--; ! } !} !!

23

Схема загрузки одного тайла Старт

Обновился?

Есть в памяти?

Есть на диске?

Сходить в диск

Сходить в сеть

Конец

Вернуть тайл

Декодировать и положить в память

Вернуть тайл

Вернуть тайл

Декодировать и положить в память

Нет

Да

Да

Да

Нет

Нет

24

Загрузка одного тайла

NetTileProvider

Один сырой тайл

Запрос одной версии тайла из сети

RawTileLoader

Поток версий сырых данных

Кеширование на диске Версионирование

TileLoader

Поток версий готовых тайлов

Кеширование в памяти Декодирование

25

RawTileLoader Задача – вернуть поток версий сырых данных

MultiFuture<RawTile> rawTileVersions(int x, y) { ! return async<RawTile>([=](MultiPromise<RawTile>* output) {! std::string currentVer, currentEtag; ! RawTile rawTile; !! if (rawTile = readFromDisk(x, y)) { ! currentVer = rawTile->version; ! currentEtag = rawTile->etag; ! output->yield(rawTile); ! } !! for (const auto& ver : versions_.subscribe()) { ! rawTile = loadFromNetwork(x, y, ver, currentEtag); !! writeToDisk(x, y, rawTile); !! currentVer = rawTile->version; ! currentEtag = rawTile->etag; ! output->yield(rawTile); ! } ! }); // async !} !

26

TileLoader

Задача – вернуть поток версий одного тайла

MultiFuture<Tile> tileVersions(int x, y) { ! return async<Tile>([=](MultiPromise<Tile>* output) { ! Tile tile = memCache->get(x, y); ! if (tile) ! output->yield(tile); !! for (const auto& rawTile : rawTileVersions(x, y)) { ! tile.version = rawTile->version; ! if (tile.etag != rawTile->etag) { ! tile.etag = rawTile->etag; ! tile.data = decoder_(rawTile->rawData); ! } !! output->yield(tile); ! memCache_->set(x, y, tile); ! } ! }); // async !} !

27

Линейный код из примера

void loadTile(int x, int y, output_iterator output) { ! if (auto tile = inMemory(x, y)) { ! output << tile; ! } !! if (auto tile = readFromDisk(x, y)) { ! output << decode(tile); ! prevVersion = tile.version; ! } !! while (true) { ! Tile tile = readFromNet(x, y, prevVersion); ! if (tile.version == prevVersion) continue; ! prevVersion = tile.version; ! output << decode(tile); ! } !} ! !

28

Мы пишем понятный и выразительный код

И вам желаем того жеJ

29

Спасибо за внимание!

30

Дмитрий Жестилевский

Яндекс.Карты

gordon@yandex-team.ru

Руководитель группы

@dr_zhest