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

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

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

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

More Decks by Python Community Chelyabinsk

Other Decks in Programming

Transcript

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

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

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

    Первоначальная реализация написана Гвидо Ван Россумом лично. 4. Гибрид.
  4. Стандарт Tornado: AsyncIOMainLoop (new in 3.2, default since 5.0) Twisted:

    asyncioreactor (new in 16.5.0, not default yet) Gevent: aiogevent (сторонний проект)
  5. 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)
  6. 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)
  7. 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)
  8. 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)
  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. 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)
  12. 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)
  13. 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
  14. 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?
  15. 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!!!
  16. 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) )
  17. 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) )
  18. 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) )
  19. 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)
  20. 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)
  21. 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'
  22. 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()
  23. 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[:] = []
  24. 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()
  25. 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()
  26. 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()
  27. 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()
  28. 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)
  29. 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
  30. 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
  31. 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)
  32. 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)
  33. 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)
  34. 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
  35. 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)
  36. 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()
  37. 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)
  38. 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)
  39. 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)
  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. 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[:] = []
  42. 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) ...
  43. 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)
  44. 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()
  45. 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)
  46. Ссылки 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 Юрий Селиванов