Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Александр Меренков. Что внутри asyncio

Александр Меренков. Что внутри asyncio

В своём докладе я постараюсь ответить на следующие вопросы:
* Какие мотивы стояли за созданием библиотеки asyncio?
* Как в ней реализовано асинхронное выполнение кода?
* Почему это полезно знать, если пишешь код с её использованием?

Transcript

  1. Что внутри Asyncio?

  2. Зачем нам асинхронность? Чтобы пока один кусок кода ожидает ввода/вывода

    (БД,сеть, пользователи), другие куски кода могли выполняться.
  3. None
  4. Вариантики 1. Threads 2. Micro-threads (Gevent, Stackless Python) 3. Callbacks

    (Twisted, Tornado, asyncio) 4. Coroutines (Twisted, Tornado, asyncio, сurio, trio)
  5. asyncio 1. Библиотека асинхронного ввода/вывода. 2. Описывается PEP 3156. 3.

    Первоначальная реализация написана Гвидо Ван Россумом лично. 4. Гибрид.
  6. Зачем asyncio? Стандартные модули 1. asyncore and asynchat Сторонние фреймворки

    1. Twisted 2. Tornado 3. Gevent
  7. Стандарт Tornado: AsyncIOMainLoop (new in 3.2, default since 5.0) Twisted:

    asyncioreactor (new in 16.5.0, not default yet) Gevent: aiogevent (сторонний проект)
  8. Как оно работает? 1. Pluggable EventLoop (uvloop, asyncio-tokio) 2. Future

    3. Task
  9. class Loop: def __init__(self): self.ready = collections.deque() def call_soon(self, callback,

    *args): self.ready.append((callback, args)) def run_until_complete(self, callback, *args): self.call_soon(callback, *args) while self.ready: ntodo = len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args)
  10. class Loop: def __init__(self): self.ready = collections.deque() def call_soon(self, callback,

    *args): self.ready.append((callback, args)) def run_until_complete(self, callback, *args): self.call_soon(callback, *args) while self.ready: ntodo = len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args)
  11. class Loop: def __init__(self): self.ready = collections.deque() def call_soon(self, callback,

    *args): self.ready.append((callback, args)) def run_until_complete(self, callback, *args): self.call_soon(callback, *args) while self.ready: ntodo = len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args)
  12. class Loop: def __init__(self): self.ready = collections.deque() def call_soon(self, callback,

    *args): self.ready.append((callback, args)) def run_until_complete(self, callback, *args): self.call_soon(callback, *args) while self.ready: ntodo = len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args)
  13. class Loop: def __init__(self): self.ready = collections.deque() def call_soon(self, callback,

    *args): self.ready.append((callback, args)) def run_until_complete(self, callback, *args): self.call_soon(callback, *args) while self.ready: ntodo = len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args)
  14. class Loop: def __init__(self): self.ready = collections.deque() def call_soon(self, callback,

    *args): self.ready.append((callback, args)) def run_until_complete(self, callback, *args): self.call_soon(callback, *args) while self.ready: ntodo = len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args)
  15. Интерлюдия Стэк вызовов, исключения и контекстные менеджеры

  16. def maybe_print(msg): if random.randint(0, 1): raise Exception(msg) else: print(msg) def

    vader(loop): print('Luke, I am your father!') loop.call_soon(maybe_print, 'Noooooooo!!!') def main(loop): loop.call_soon(vader, loop) loop.call_soon(vader, loop) Loop().run_until_complete(main, loop)
  17. def run_until_complete(self, callback, *args): self.call_soon(callback, *args) # main, loop while

    self.ready: ntodo = len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args) # main(loop)
  18. def run_until_complete(self, callback, *args): self.call_soon(callback, *args) while self.ready: ntodo =

    len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args) # vader(loop) x 2
  19. def run_until_complete(self, callback, *args): self.call_soon(callback, *args) while self.ready: ntodo =

    len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args) # maybe_print(msg) x 2?
  20. Luke, I am your father! Luke, I am your father!

    Traceback (most recent call last): File "base_loop.py", line 42, in <module> loop.run_until_complete(main, loop) File "base_loop.py", line 17, in run_until_complete callback(*args) File "base_loop.py", line 29, in maybe_print raise Exception(msg) Exception: Noooooooo!!!
  21. def main(loop): try: loop.call_soon(vader, loop) loop.call_soon(vader, loop) except: pass Loop().run_until_complete(main,

    loop)
  22. def main(loop): with open('file.txt', 'rb') as f: loop.call_soon(process_file, f) Loop().run_until_complete(main,

    loop)
  23. Итог интерлюдии: Коллбэки не очень вписываются в структуру языка.

  24. Снова цикл событий Событие - это всё, что происходит где-то

    не в нашем коде
  25. selectors Позволяет интерпретатору узнать у операционной системы наступило ли интересующее

    нас событие
  26. class EventLoop: def __init__(self): self.ready = collections.deque() self.selector = selectors.DefaultSelector()

    def add_reader(self, sock, callback): self.selector.register( sock, EVENT_READ, (self._accept_conn, sock, callback) ) def _accept_conn(self, sock, callback): conn, addr = sock.accept() conn.setblocking(False) self.selector.register( conn, EVENT_READ, (callback, conn) )
  27. class EventLoop: def __init__(self): self.ready = collections.deque() self.selector = selectors.DefaultSelector()

    def add_reader(self, sock, callback): self.selector.register( sock, EVENT_READ, (self._accept_conn, sock, callback) ) def _accept_conn(self, sock, callback): conn, addr = sock.accept() conn.setblocking(False) self.selector.register( conn, EVENT_READ, (callback, conn) )
  28. class EventLoop: def __init__(self): self.ready = collections.deque() self.selector = selectors.DefaultSelector()

    def add_reader(self, sock, callback): self.selector.register( sock, EVENT_READ, (self._accept_conn, sock, callback) ) def _accept_conn(self, sock, callback): conn, addr = sock.accept() conn.setblocking(False) self.selector.register( conn, EVENT_READ, (callback, conn) )
  29. def run_until_complete(self, callback, *args): self.call_soon(callback, *args) while self.ready or self.selector.get_map():

    ntodo = len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args) for key, events in self.selector.select(timeout=0): callback, *args = key.data self.call_soon(callback, *args)
  30. def run_until_complete(self, callback, *args): self.call_soon(callback, *args) while self.ready or self.selector.get_map():

    ntodo = len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args) for key, events in self.selector.select(timeout=0): callback, *args = key.data self.call_soon(callback, *args)
  31. def print_data(conn): print(conn.recv(1000)) def main(loop): sock = socket.socket() sock.bind(('localhost', 8086))

    sock.listen(100) sock.setblocking(False) loop.add_reader(sock, print_data) loop = EventLoop() loop.run_until_complete(main, loop) _____________________________________________________ $: nc localhost 8086 $: python3 event_loop.py "Hi there!" b'"Hi there!"\n' "Hello!" b'"Hello!"\n' "Answer me, please!" b'"Answer me, please!"\n'
  32. А корутины? В дело вступают Task и Future

  33. def set_result(self, result): self._result = result self.state = 'FINISHED' self.schedule_callbacks()

    def cancel(self): self.state = 'CANCELLED' self.schedule_callbacks() def result(self): if self.state == 'CANCELLED': raise CancelledError if self.exception is not None: raise self.exception return self._result def __await__(self): if self.state == 'PENDING': yield self return self.result() class Future: state = 'PENDING' # FINISHED, CANCELLED callbacks = [] exception = None _result = None def __init__(self, loop): self.loop = loop self.source_traceback = extract_stack(sys.getframe(1)) def add_done_callback(self, callback): self.callbacks.append(callback) def _schedule_callbacks(self): for callback in self.callbacks: self.loop.call_soon(callback, self) self.callbacks[:] = [] def set_exception(self, exception): self.exception = exception self.state = 'FINISHED' self.schedule_callbacks()
  34. class Future: state = 'PENDING' # FINISHED, CANCELLED callbacks =

    [] exception = None _result = None def __init__(self, loop): self.loop = loop self.source_traceback = extract_stack(sys.getframe(1)) def add_done_callback(self, callback): self.callbacks.append(callback) def schedule_callbacks(self): for callback in self.callbacks: self.loop.call_soon(callback, self) self.callbacks[:] = []
  35. def set_exception(self, exception): self.exception = exception self.state = 'FINISHED' self.schedule_callbacks()

    def set_result(self, result): self._result = result self.state = 'FINISHED' self.schedule_callbacks() def cancel(self): self.state = 'CANCELLED' self.schedule_callbacks()
  36. def result(self): if self.state == 'CANCELLED': raise CancelledError if self.exception

    is not None: raise self.exception return self._result def __await__(self): if self.state == 'PENDING': yield self return self.result()
  37. class Task(Future): def __init__(self, coro, *, loop=None): super().__init__(loop=loop) self.coro =

    coro def step(self, exc=None): try: if exc is None: result = self.coro.send(None) else: self.coro.throw(exc) except StopIteration: result = None except Exception as exc: self.set_exception(exc) else: if isinstance(result, Future): result.add_done_callback(self.wakeup) elif result is None: self.loop.call_soon(self.step) def wakeup(self, future): try: future.result() except Exception as exc: self.step(exc) else: self.step()
  38. class Task(Future): def __init__(self, coro, *, loop=None): super().__init__(loop=loop) self.coro =

    coro def wakeup(self, future): try: future.result() except Exception as exc: self.step(exc) else: self.step()
  39. def step(self, exc=None): try: if exc is None: result =

    self.coro.send(None) else: self.coro.throw(exc) except StopIteration: result = None except Exception as exc: self.set_exception(exc) else: if isinstance(result, Future): result.add_done_callback(self.wakeup) elif result is None: self.loop.call_soon(self.step)
  40. def sock_accept(self, sock, fut=None): fut = fut if fut else

    Future(loop=self) try: conn, address = sock.accept() conn.setblocking(False) except (BlockingIOError, InterruptedError): self.selector.register( sock, EVENT_READ, (self.sock_accept, sock, fut) ) except Exception as exc: fut.set_exception(exc) self.selector.unregister(sock) else: fut.set_result((conn, address)) self.selector.unregister(sock) return fut
  41. def sock_recv(self, sock, n, fut=None): fut = fut if fut

    else Future(loop=self) try: data = sock.recv(n) except (BlockingIOError, InterruptedError): self.selector.register( sock, EVENT_READ, (self.sock_recv, sock, n, fut) ) except Exception as exc: fut.set_exception(exc) self.selector.unregister(sock) else: fut.set_result(data) self.selector.unregister(sock) return fut
  42. async def main(loop): sock = socket.socket() sock.bind(('localhost', 8080)) sock.listen(100) sock.setblocking(False)

    conn, addr = await loop.sock_accept(sock) result = await loop.sock_recv(conn, 1000) print(result) loop = EventLoop() task = Task(coro=main(loop), loop=loop) loop.run_until_complete(task.step)
  43. def step(self, exc=None): try: if exc is None: result =

    self.coro.send(None) else: self.coro.throw(exc) except StopIteration: result = None except Exception as exc: self.set_exception(exc) else: if isinstance(result, Future): result.add_done_callback(self.wakeup) elif result is None: self.loop.call_soon(self.step)
  44. async def main(loop): sock = socket.socket() sock.bind(('localhost', 8080)) sock.listen(100) sock.setblocking(False)

    conn, addr = await loop.sock_accept(sock) result = await loop.sock_recv(conn, 1000) print(result)
  45. def sock_accept(self, sock, fut=None): fut = fut if fut else

    Future(loop=self) try: conn, address = sock.accept() conn.setblocking(False) except (BlockingIOError, InterruptedError): self.selector.register( sock, EVENT_READ, (self.sock_accept, sock, fut) ) except Exception as exc: fut.set_exception(exc) self.selector.unregister(sock) else: fut.set_result((conn, address)) self.selector.unregister(sock) return fut
  46. async def main(loop): sock = socket.socket() sock.bind(('localhost', 8080)) sock.listen(100) sock.setblocking(False)

    conn, addr = await loop.sock_accept(sock) result = await loop.sock_recv(conn, 1000) print(result)
  47. class Future: def result(self): if self.state == 'CANCELLED': raise CancelledError

    if self.exception is not None: raise self.exception return self._result def __await__(self): if self.state == 'PENDING': yield self return self.result()
  48. async def main(loop): sock = socket.socket() sock.bind(('localhost', 8080)) sock.listen(100) sock.setblocking(False)

    conn, addr = await loop.sock_accept(sock) result = await loop.sock_recv(conn, 1000) print(result)
  49. def step(self, exc=None): try: if exc is None: result =

    self.coro.send(None) else: self.coro.throw(exc) except StopIteration: result = None except Exception as exc: self.set_exception(exc) else: if isinstance(result, Future): result.add_done_callback(self.wakeup) elif result is None: self.loop.call_soon(self.step)
  50. def run_until_complete(self, callback, *args): self.call_soon(callback, *args) while self.ready or self.selector.get_map():

    ntodo = len(self._ready) for _ in range(ntodo): callback, args = self.ready.popleft() callback(*args) for key, events in self.selector.select(timeout=0): callback, *args = key.data # sock_accept [sock, fut] self.call_soon(callback, *args)
  51. def sock_accept(self, sock, fut=None): fut = fut if fut else

    Future(loop=self) try: conn, address = sock.accept() conn.setblocking(False) except (BlockingIOError, InterruptedError): self.selector.register( sock, EVENT_READ, (self.sock_accept, sock, fut) ) except Exception as exc: fut.set_exception(exc) self.selector.unregister(sock) else: fut.set_result((conn, address)) self.selector.unregister(sock) return fut
  52. class Future: def set_result(self, result): self._result = result self.state =

    'FINISHED' self.schedule_callbacks() # Task.wakeup def _schedule_callbacks(self): for callback in self.callbacks: self.loop.call_soon(callback, self) self.callbacks[:] = []
  53. def wakeup(self, future): try: future.result() except Exception as exc: self.step(exc)

    else: self.step() def step(self, exc=None): try: if exc is None: result = self.coro.send(None) ...
  54. async def main(loop): sock = socket.socket() sock.bind(('localhost', 8080)) sock.listen(100) sock.setblocking(False)

    conn, addr = await loop.sock_accept(sock) result = await loop.sock_recv(conn, 1000) print(result)
  55. class Future: def result(self): if self.state == 'CANCELLED': raise CancelledError

    if self.exception is not None: raise self.exception return self._result def __await__(self): if self.state == 'PENDING': yield self return self.result()
  56. async def main(loop): sock = socket.socket() sock.bind(('localhost', 8080)) sock.listen(100) sock.setblocking(False)

    conn, addr = await loop.sock_accept(sock) result = await loop.sock_recv(conn, 1000) print(result)
  57. Цель Предоставить базу для создания асинхронных фреймворков максимально совместимую с

    уже существующими решениями.
  58. Особенности реализации В асинкайо объединены два подхода к обеспечению асинхронности,

    причем второй реализован поверх первого.
  59. Следствиe Коллбэчное основание привносит в реализацию лишнюю сложность.

  60. None
  61. “All non-trivial abstractions, to some degree, are leaky”

  62. StreamWriter

  63. Вопросы?

  64. Ссылки 1. Some thoughts on asynchronous API design in a

    post-async/await world by Nathaniel J. Smith 2. Playing with asyncio by Nathan Hoad 3. I don't understand Python's Asyncio by Armin Ronacher 4. The report of our death by Glyph Lefkowitz 5. Trio: Async concurrency for mere mortals by Nathaniel J. Smith 6. Fear and Awaiting in Async: A Savage Journey to the Heart of the Coroutine Dream by David Beazley 7. Die Threads by David Beazley 8. Asyncio сегодня и завтра by Юрий Селиванов 9. Подводные камни asyncio by Андрей Светлов 10. Разница между yield и yield from by Guido van Rossum 11. uvloop: Blazing fast python networking by Юрий Селиванов
  65. None
  66. None
  67. None
  68. None