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. Что внутри Asyncio?

    View full-size slide

  2. Зачем нам асинхронность?
    Чтобы пока один кусок кода
    ожидает ввода/вывода (БД,сеть,
    пользователи), другие куски
    кода могли выполняться.

    View full-size slide

  3. Вариантики
    1. Threads
    2. Micro-threads (Gevent, Stackless Python)
    3. Callbacks (Twisted, Tornado, asyncio)
    4. Coroutines (Twisted, Tornado, asyncio, сurio,
    trio)

    View full-size slide

  4. asyncio
    1. Библиотека асинхронного ввода/вывода.
    2. Описывается PEP 3156.
    3. Первоначальная реализация написана
    Гвидо Ван Россумом лично.
    4. Гибрид.

    View full-size slide

  5. Зачем asyncio?
    Стандартные модули
    1. asyncore and asynchat
    Сторонние фреймворки
    1. Twisted
    2. Tornado
    3. Gevent

    View full-size slide

  6. Стандарт
    Tornado: AsyncIOMainLoop (new in 3.2, default since 5.0)
    Twisted: asyncioreactor (new in 16.5.0, not default yet)
    Gevent: aiogevent (сторонний проект)

    View full-size slide

  7. Как оно работает?
    1. Pluggable EventLoop (uvloop, asyncio-tokio)
    2. Future
    3. Task

    View full-size slide

  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)

    View full-size slide

  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)

    View full-size slide

  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)

    View full-size slide

  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)

    View full-size slide

  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)

    View full-size slide

  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)

    View full-size slide

  14. Интерлюдия
    Стэк вызовов, исключения и
    контекстные менеджеры

    View full-size slide

  15. 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)

    View full-size slide

  16. 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)

    View full-size slide

  17. 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

    View full-size slide

  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) # maybe_print(msg) x 2?

    View full-size slide

  19. Luke, I am your father!
    Luke, I am your father!
    Traceback (most recent call last):
    File "base_loop.py", line 42, in
    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!!!

    View full-size slide

  20. def main(loop):
    try:
    loop.call_soon(vader, loop)
    loop.call_soon(vader, loop)
    except:
    pass
    Loop().run_until_complete(main, loop)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  25. 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)
    )

    View full-size slide

  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)
    )

    View full-size slide

  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)
    )

    View full-size slide

  28. 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)

    View full-size slide

  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)

    View full-size slide

  30. 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'

    View full-size slide

  31. А корутины?
    В дело вступают Task и Future

    View full-size slide

  32. 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()

    View full-size slide

  33. 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[:] = []

    View full-size slide

  34. 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()

    View full-size slide

  35. 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()

    View full-size slide

  36. 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()

    View full-size slide

  37. 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()

    View full-size slide

  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)

    View full-size slide

  39. 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

    View full-size slide

  40. 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

    View full-size slide

  41. 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)

    View full-size slide

  42. 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)

    View full-size slide

  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)

    View full-size slide

  44. 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

    View full-size slide

  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)

    View full-size slide

  46. 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()

    View full-size slide

  47. 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)

    View full-size slide

  48. 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)

    View full-size slide

  49. 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)

    View full-size slide

  50. 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

    View full-size slide

  51. 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[:] = []

    View full-size slide

  52. 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)
    ...

    View full-size slide

  53. 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)

    View full-size slide

  54. 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()

    View full-size slide

  55. 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)

    View full-size slide

  56. Цель
    Предоставить базу для создания
    асинхронных фреймворков максимально
    совместимую с уже существующими
    решениями.

    View full-size slide

  57. Особенности реализации
    В асинкайо объединены два подхода к
    обеспечению асинхронности, причем
    второй реализован поверх первого.

    View full-size slide

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

    View full-size slide

  59. “All non-trivial
    abstractions, to
    some degree, are
    leaky”

    View full-size slide

  60. StreamWriter

    View full-size slide

  61. Вопросы?

    View full-size slide

  62. Ссылки
    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 Юрий Селиванов

    View full-size slide