1
2 
карты 
Асинхронная 
разработка на C++ 
Дмитрий Жестилевский
3 
Содержание 
! Как работает загрузка карты 
! Стейт-машина и линейный код 
! С++11 concurrency 
! Наш вариант concurrency 
! Пример реализации загрузки карты
4
5 
Тайл 
! Можно отменять 
! Должен кешироваться 
! Должен обновляться 
! Один тайл может содержать множество версий
6 
Схема загрузки одного тайла 
Старт 
Есть 
в памяти? 
Сходить в диск 
Есть 
на диске? 
Сходить в сеть 
Обновился? 
Конец 
Вернуть 
тайл 
Декодировать 
и положить в память 
Вернуть 
тайл 
Вернуть 
тайл 
Декодировать 
и положить в память 
Нет 
Да 
Да 
Да 
Нет 
Нет
7 
Конечный автомат 
Он же стейт-машина
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 >! 
8
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
Promise<int> promise;! 
11 
Future/Promise 
Promise<int> p;! 
Future<int> f = p.future();! 
Future Promise 
auto future = startAsync();! 
// ...! 
// ...! 
int value = future.get();! 
int val = calc();! 
std::cout << value;!promise.set(val);!
12 
Использование Future 
output 
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 
}
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

Дмитрий Жестилевский — Yet another threading framework: асинхронная разработка на C++ под мобильные устройства

  • 1.
  • 2.
    2 карты Асинхронная разработка на C++ Дмитрий Жестилевский
  • 3.
    3 Содержание !Как работает загрузка карты ! Стейт-машина и линейный код ! С++11 concurrency ! Наш вариант concurrency ! Пример реализации загрузки карты
  • 4.
  • 5.
    5 Тайл !Можно отменять ! Должен кешироваться ! Должен обновляться ! Один тайл может содержать множество версий
  • 6.
    6 Схема загрузкиодного тайла Старт Есть в памяти? Сходить в диск Есть на диске? Сходить в сеть Обновился? Конец Вернуть тайл Декодировать и положить в память Вернуть тайл Вернуть тайл Декодировать и положить в память Нет Да Да Да Нет Нет
  • 7.
    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 >! 8
  • 9.
    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.
  • 11.
    Promise<int> promise;! 11 Future/Promise Promise<int> p;! Future<int> f = p.future();! Future Promise auto future = startAsync();! // ...! // ...! int value = future.get();! int val = calc();! std::cout << value;!promise.set(val);!
  • 12.
    12 Использование Future output 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 }
  • 13.
    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.
    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.
    15 Чего нетв std:: ! Масштабируемость ! Отменяемость асинхронных операций ! Генераторы — функции, возвращающие множество результатов
  • 16.
    16 Контексты исполнения ! Потоки ! Процессы
  • 17.
    17 Coroutine –user-space thread Пишем асинхронный код синхронно
  • 18.
    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.
    19 Модель потоков Все взаимодействие с IO – в отдельных выделенных потоках (сеть, диск) Все остальное – в глобальном thread pool
  • 20.
    20 Отмена операций ! Отмена через exception (Future.reset, ~Future) ! Cancellation points: wait, sleep, get ! Мгновенная отмена или ленивая?
  • 21.
  • 22.
    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.
    23 Схема загрузкиодного тайла Старт Есть в памяти? Сходить в диск Есть на диске? Сходить в сеть Обновился? Конец Вернуть тайл Декодировать и положить в память Вернуть тайл Вернуть тайл Декодировать и положить в память Нет Да Да Да Нет Нет
  • 24.
    24 Загрузка одноготайла NetTileProvider Один сырой тайл Запрос одной версии тайла из сети RawTileLoader Кеширование на диске Версионирование Поток версий сырых данных TileLoader Кеширование в памяти Декодирование Поток версий готовых тайлов
  • 25.
    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.
    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.
    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.
    28 Мы пишемпонятный и выразительный код И вам желаем того жеJ
  • 29.
    29 Спасибо завнимание!
  • 30.
    30 Дмитрий Жестилевский Яндекс.Карты Руководитель группы gordon@yandex-team.ru @dr_zhest