Григорий Демченко, Асинхронность и неблокирующая...

43

Upload: sergey-platonov

Post on 06-Jan-2017

181 views

Category:

Software


3 download

TRANSCRIPT

Page 1: Григорий Демченко, Асинхронность и неблокирующая синхронизация
Page 2: Григорий Демченко, Асинхронность и неблокирующая синхронизация

Асинхронность и неблокирующая синхронизация

Григорий Демченко, 2016

Логотип партнера

Page 3: Григорий Демченко, Асинхронность и неблокирующая синхронизация

План

▌Простой сервер

▌Сопрограммы

▌Планировщики

▌Синхронизация

▌Заключение

Page 4: Григорий Демченко, Асинхронность и неблокирующая синхронизация

Простой сервер

Асинхронность и неблокирующая синхронизация

4

Page 5: Григорий Демченко, Асинхронность и неблокирующая синхронизация

5

Синхронный многопоточный серверvoid multitheadedServer() { Acceptor acceptor{80}; // слушаем 80 порт while (true) { auto socket = acceptor.accept(); // 1-й блокирующий вызов thread([socket] { auto request = socket.read(); // 2-й блокирующий вызов auto response = handleRequest(request); socket.write(response); // 3-й блокирующий вызов }).detach(); }}

Page 6: Григорий Демченко, Асинхронность и неблокирующая синхронизация

6

Асинхронный серверvoid asynchronousServer() { Acceptor acceptor{80}; acceptor.onAccept([] (auto socket) { // 1-й асинхронный вызов socket.onRead([socket](auto request) { // 2-й асинхронный вызов auto response = handleRequest(request); socket.onWrite(response, []() { // 3-й асинхронный вызов // завершение }); }); // продолжаем принимать соединения... });}

Page 7: Григорий Демченко, Асинхронность и неблокирующая синхронизация

7

Реальный асинхронный серверvoid realAsynchronousServer() { Acceptor acceptor{80}; Handler accepting = [&acceptor, &accepting] { struct Connection { explicit Connection(Socket sock) : socket(std::move(sock)) {}

void onConnect() { if (error) return onError(error); socket.onRead([this](const Request& request, const Error& error) { if (error) return onError(error); response = handlerRequest(request); socket.onWrite(response, [this](const Error& error) { if (error) return onError(error); onDone(); }); }); }

private: void onError(const Error& error) { delete this; }

void onDone() { delete this; }

Response response; Socket socket; };

acceptor.onAccept([&accepting](Socket socket, const Error& error) { if (!error) { (new Connection{socket})->onConnect(); accepting(); } }); };}

▌Сравните:void asynchronousServer() { Acceptor acceptor{80}; acceptor.onAccept([] (socket) { socket.onRead([socket](request) { response = handleRequest(request); socket.onWrite(response, []() { // завершение }); }); // продолжаем принимать соединения });}

Page 8: Григорий Демченко, Асинхронность и неблокирующая синхронизация

8

Асинхронный сервер: обсуждение▌Плюсы:

› Производительность

› Автоматическая параллелизация исполнения

▌Минусы:

› Сложность:

1. Нелинейный рост сложности и проблем2. Явная передача контекста исполнения3. Обработка ошибок4. Время жизни объектов5. Отладка

Page 9: Григорий Демченко, Асинхронность и неблокирующая синхронизация

9

Что бы хотелось?

▌Использовать эффективность асинхронного подхода

▌Использовать простоту синхронного подхода

Page 10: Григорий Демченко, Асинхронность и неблокирующая синхронизация

│ Решение: использовать │ сопрограммы

10

Page 11: Григорий Демченко, Асинхронность и неблокирующая синхронизация

Сопрограммы

Асинхронность и неблокирующая синхронизация

Page 12: Григорий Демченко, Асинхронность и неблокирующая синхронизация

Сопрограммы▌Подпрограмма: полное выполнение действий▌Сопрограмма: возможно частичное выполнение действий

Т.е. сопрограммы обобщают подпрограммы

Примеры сопрограмм:

▌Генераторы на языке Python.▌Async/await C++ proposal.

Метод сохранения контекста исполнения:

▌Stackful сопрограммы.

12

Page 13: Григорий Демченко, Асинхронность и неблокирующая синхронизация

13

Реализация сопрограммПроблема▌Сопрограммы всё еще не являются частью языка▌Необходимо применять низкоуровневые примитивы.

Решение: boost.context:▌Эффективное решение: использование ассемблера▌Десктопные платформы: Windows, Linux, MacOSX▌Мобильные платформы: Windows Phone, Android, IOS▌Процессоры: x86, x86_64, ARM, Spark, Spark64, PPC, PPC64, MIPS▌Компиляторы: GCC, Clang, Visual Studio

Page 14: Григорий Демченко, Асинхронность и неблокирующая синхронизация

14

boost.context▌make_fcontext: создает контекст▌jump_fcontext: переключает контекст

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

▌1. Аллокация памяти под стек▌2. Создание контекста:

coroCtx = make_fcontext(stackPtr, stackSize, coroCb)

▌3. Запуск или продолжение исполнения:

jump_fcontext(callerCtx, coroCtx, opaquePtr)

▌4. Возвращение или заморозка исполнения:

jump_fcontext(coroCtx, callerCtx, opaquePtr)

Page 15: Григорий Демченко, Асинхронность и неблокирующая синхронизация

15

Сопрограммы в действииvoid coroFun() { log << "2"; yield(); log << "4";}

log << "1";Coro c{coroFun};log << "3";c.resume();log << "5";

Вывод:▌12345

Page 16: Григорий Демченко, Асинхронность и неблокирующая синхронизация

16

Использование сопрограммыПреобразовать асинхронный вызов:

async(completionCallback);

В синхронный:synca(); // async -> synca

Используя следующий подход:

void synca() { auto& coro = currentCoro(); async([&coro] { coro.resume(); }); yield();}

Page 17: Григорий Демченко, Асинхронность и неблокирующая синхронизация

17

Sequence Diagram

Page 18: Григорий Демченко, Асинхронность и неблокирующая синхронизация

18

Проблема: состояние гонки

Page 19: Григорий Демченко, Асинхронность и неблокирующая синхронизация

19

Возможные решения

▌std::mutex

▌boost::asio::strand

▌defer

Page 20: Григорий Демченко, Асинхронность и неблокирующая синхронизация

20

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

▌defer откладывает асинхронный вызов:

using Handler = std::function<void()>;void defer(Handler handler);

Page 21: Григорий Демченко, Асинхронность и неблокирующая синхронизация

21

Решение: defer

Page 22: Григорий Демченко, Асинхронность и неблокирующая синхронизация

22

Использование: defervoid synca() { auto& coro = currentCoro(); async([&coro] { coro.resume(); }); yield();}

void synca() { auto& coro = currentCoro(); defer([&coro] { async([&coro] { coro.resume(); }); });}

Page 23: Григорий Демченко, Асинхронность и неблокирующая синхронизация

23

Абстрагирование от сопрограммvoid synca() { auto& coro = currentCoro(); defer([&coro] { async([&coro] { coro.resume(); }); });}

void synca() { deferProceed([](Handler proceed) { async(proceed); });}

Page 24: Григорий Демченко, Асинхронность и неблокирующая синхронизация

24

Synca сервер

void syncaServer() { Acceptor acceptor{80}; // слушаем 80 порт while (true) { auto socket = acceptor.accept(); // 1-й неблокирующий вызов go([socket] { // стартуем новую сопрограмму auto request = socket.read(); // 2-й неблокирующий вызов auto response = handleRequest(request); socket.write(response); // 3-й неблокирующий вызов }); }}

Page 25: Григорий Демченко, Асинхронность и неблокирующая синхронизация

Планировщики

Асинхронность и неблокирующая синхронизация

Page 26: Григорий Демченко, Асинхронность и неблокирующая синхронизация

Планировщик

▌Интерфейс планировщика:

struct IScheduler { virtual void schedule(Handler) = 0;};

Плашировщик запускает обработчики.

Page 27: Григорий Демченко, Асинхронность и неблокирующая синхронизация

27

Пул потоков

struct ThreadPool : IScheduler { explicit ThreadPool(size_t threads);

void schedule(Handler handler) { service.post(std::move(handler)); }private: boost::asio::io_service service;};

Page 28: Григорий Демченко, Асинхронность и неблокирующая синхронизация

28

Сопрограммы и планировщикstruct Journey { void defer(Handler handler) { deferHandler = std::move(handler); yield(); } void proceed() { scheduler->schedule([this] { coro.resume(); deferHandler(); }); }private: IScheduler* scheduler; Coro coro; Handler deferHandler;};

Page 29: Григорий Демченко, Асинхронность и неблокирующая синхронизация

29

Телепортация▌Давайте перепрыгнем на другой планировщик:

go([&] { log << "Inside 1st thread pool"; teleport(tp2); log << “Inside 2nd thread pool";}, tp1);

▌Реализация:

void Journey::teleport(IScheduler& s) { scheduler = &s; defer([this] { proceed(); });}

Page 30: Григорий Демченко, Асинхронность и неблокирующая синхронизация

30

Телепортация: Sequence Diagram

Page 31: Григорий Демченко, Асинхронность и неблокирующая синхронизация

31

Порталы▌Портал – это RAII телепортация:

struct Portal { Portal(IScheduler& destination) : source{currentScheduler()} { teleport(destination); } ~Portal() { teleport(source); }private: IScheduler& source;};

Page 32: Григорий Демченко, Асинхронность и неблокирующая синхронизация

32

Использование порталовstruct Network { void handleNetworkEvents() { // действия внутри сетевого пула потоков }};

ThreadPool commonPool{4};ThreadPool networkPool{2};

portal<Network>().attach(networkPool);go([] { log << "Inside common pool"; portal<Network>()->handleNetworkEvents(); log << "Inside common pool";}, commonPool);

Page 33: Григорий Демченко, Асинхронность и неблокирующая синхронизация

│ Портал – абстракция│ среды исполнения

33

Page 34: Григорий Демченко, Асинхронность и неблокирующая синхронизация

Синхронизация

Асинхронность и неблокирующая синхронизация

Page 35: Григорий Демченко, Асинхронность и неблокирующая синхронизация

35

Alone

struct Alone : IScheduler { void schedule(Handler handler) { strand.post(std::move(handler)); }private: boost::asio::io_service::strand strand;};

strand гарантирует, что ни один обработчик не будет запущен параллельно с другим

Page 36: Григорий Демченко, Асинхронность и неблокирующая синхронизация

│ Alone – это│ неблокирующая│ синхронизация│ без дедлоков

36

Page 37: Григорий Демченко, Асинхронность и неблокирующая синхронизация

37

Интегральный пример: инициализацияstruct DiskCache/MemCache { optional<string> get(const string& key); void set(const string& key, const string& val);};struct Network { string performRequest(const std::string& key);};

ThreadPool commonPool{3}; // common operations poolThreadPool diskPool{2}; // disk operations poolThreadPool netPool{1}; // network operations poolAlone memAlone{commonPool}; // MemCache access synchronization

portal<DiskCache>().attach(diskPool);portal<MemCache>().attach(memAlone);portal<Network>().attach(netPool);

Page 38: Григорий Демченко, Асинхронность и неблокирующая синхронизация

38

Интегральный пример: получение значенияstring obtainValue(const string& key) { auto res = portal<MemCache>()->get(key); if (res) return *res; res = portal<DiskCache>()->get(key); if (res) return *res; auto val = portal<Network>()->performRequest(key); go([key, val] { portal<MemCache>()->set(key, val); portal<DiskCache>()->set(key, val); }); return val;}

Page 39: Григорий Демченко, Асинхронность и неблокирующая синхронизация

39

Свойства портала

▌Работает с исключениями▌Абстрагирует контекст исполнения

Page 40: Григорий Демченко, Асинхронность и неблокирующая синхронизация

Выводы

Асинхронность и неблокирующая синхронизация

Page 41: Григорий Демченко, Асинхронность и неблокирующая синхронизация

41

ТеоремаЛюбая асинхронная задача может быть реализована через сопрограммы

▌1. Нет кода после вызова:

// код до async(params..., cb); // отсутствует код

// код до synca(params...); cb();

▌2. Есть код после вызова:

// код до async(..., cb); // код после

// код до go { async(..., cb); }; // код после

// код до go { synca(...); cb(); }; // код после

Page 42: Григорий Демченко, Асинхронность и неблокирующая синхронизация

42

Выводы▌Синхронный подход: простота▌Асинхронный подход : эффективность▌Неблокирующая синхронизация без дедлоков▌Работает с исключениями▌Прозрачно для использования▌Принципиально новые подходы и паттерны▌Это прикольно!

https://github.com/gridem/Synca

https://bitbucket.org/gridem/synca

Page 43: Григорий Демченко, Асинхронность и неблокирующая синхронизация

Асинхронность и неблокирующая синхронизация

habrahabr.ru/users/gridem

gridem.blogspot.com github.com/gridem

bitbucket.org/gridem

Григорий Демченко, Разработчик С++

43