python meetup: coroutines, event loops, and non-blocking i/o

29
Tikitu de Jager • @tTikitu • [email protected] going async coroutines event loops non-blocking I/O PUN • Utrecht • 20-6-2014

Upload: buzzcapture

Post on 06-May-2015

682 views

Category:

Engineering


6 download

DESCRIPTION

An introduction to the notions that made node.js famous: asynchronous I/O in the Python world.

TRANSCRIPT

Page 1: Python meetup: coroutines, event loops, and non-blocking I/O

Tikitu de Jager • @tTikitu • [email protected]

going asynccoroutines event loops non-blocking I/O

PUN • Utrecht • 20-6-2014

Page 2: Python meetup: coroutines, event loops, and non-blocking I/O

magicimport asyncio import asyncio_redis [email protected] def my_subscriber(channel): # Create connection connection = yield from asyncio_redis.Connection.create(host='localhost', port=6379) # Create subscriber. subscriber = yield from connection.start_subscribe() # Subscribe to channel. yield from subscriber.subscribe([channel]) # Wait for incoming events. while True: reply = yield from subscriber.next_published() print('Received: ', repr(reply.value), 'on channel', reply.channel) !loop = asyncio.get_event_loop() asyncio.async(my_subscriber('channel-1')) asyncio.async(my_subscriber('channel-2')) loop.run_forever()

source: asyncio-redis

Page 3: Python meetup: coroutines, event loops, and non-blocking I/O

non-blocking I/Oqueueing for coffee starbucks just waiting around

Page 4: Python meetup: coroutines, event loops, and non-blocking I/O

coffee as metaphor for I/O

❖ blocking I/O is queueing for coffee

❖ guy in front wants 17 litres of kopi luwak

❖ all I want is an espresso

❖ non-blocking I/O is the starbucks model

❖ give your order, then go wait somewhere

❖ they call you back when it’s ready

Page 5: Python meetup: coroutines, event loops, and non-blocking I/O

“non-blocking”?

starbucks queueing is not useful if your application is

❖ CPU-bound

❖ I/O-bound by pushing bits

it is useful if you spend most of your time waiting

doing stuff still takes time

Page 6: Python meetup: coroutines, event loops, and non-blocking I/O

waiting

most I/O is not pushing bits:

❖ server waits for connections

❖ call a service: wait for response

❖ wait for socket buffer to fill

if you’re just waiting: yield the CPU

… but then who will give it back to you? …

Page 7: Python meetup: coroutines, event loops, and non-blocking I/O

event loopdid anything happen? how about now? callback hell

Page 8: Python meetup: coroutines, event loops, and non-blocking I/O

you know this

❖ GUI programming: “when this button is clicked…”

❖ (old-fashioned) javascript onclick &c

❖ event loop checks for events and runs callbacks

❖ (select module makes polling for events easy)

Page 9: Python meetup: coroutines, event loops, and non-blocking I/O

callbacks for non-blocking I/O?

a_socket.recv(bufsize=16) ⇒

event_loop.when_socket_has_ready_buffer(a_s, 16, callback_f)

“callback hell”

Page 10: Python meetup: coroutines, event loops, and non-blocking I/O

coroutinesstop/go generators yield

Page 11: Python meetup: coroutines, event loops, and non-blocking I/O

coroutines and generators

a coroutine is a routine (function) that can pause and resume its execution

def a_coroutine(): do_some_stuff() yield do_some_more_stuff()

def a_coroutine(): do_some_stuff() yield to some_other_coroutine # invented syntax do_some_more_stuff()

A generator is a coroutine that can only yield to its caller

Page 12: Python meetup: coroutines, event loops, and non-blocking I/O

yield to the event loop

import asyncio import asyncio_redis [email protected] def my_subscriber(channels): # [snip] # Wait for incoming events. while True: reply = yield from subscriber.next_published() print('Received: ', repr(reply.value), 'on channel', reply.channel) !loop = asyncio.get_event_loop() asyncio.async(my_subscriber('channel-1')) asyncio.async(my_subscriber('channel-2')) loop.run_forever()

Page 13: Python meetup: coroutines, event loops, and non-blocking I/O

roll-your-own event loop

def loop(): while True: for coroutine in waiting_list: if io_is_ready_for(coroutine): running_list.push(coroutine) coroutine = running_list.pop() coroutine.next()

(p.s. don’t do this)

Page 14: Python meetup: coroutines, event loops, and non-blocking I/O

what’ll it be?twisted gevent asyncio node.js

Page 15: Python meetup: coroutines, event loops, and non-blocking I/O

twisted

❖ networking protocols

❖ callbacks (methods on a “protocol” class)

❖ e.g. connectionMade(self), dataReceived(self, data)

❖ “you don't port an application to Twisted: You write a Twisted application in most cases.” —Jesse Noller

❖ “deferred”: abstraction now usually called Future or Promise (proxy for a value that will be computed later)

Page 16: Python meetup: coroutines, event loops, and non-blocking I/O

asyncio

❖ similar high-level protocols but also

❖ intends to provide a base layer for other libraries

❖ yield from

❖ python3 stdlib

❖ (how I got interested in this whole business)

Page 17: Python meetup: coroutines, event loops, and non-blocking I/O

yield from

event_loop.please_run_for_me(a_generator()) !def a_generator(): for val in nested_generator(): yield val !def nested_generator(): for val in deeper_nested_generator(): yield val !def deeper_nested_generator(): event_loop.register(self, for_io_op='recv', on_socket=a_socket) yield return a_socket.recv()

… but what if we have to support generator send()?

def a_function(): nested_function() def nested_function(): deeper_nested_function() def deeper_nested_function(): return a_socket.recv()

Page 18: Python meetup: coroutines, event loops, and non-blocking I/O

send() the wrong waygen = a_generator() gen.next() gen.send(1) gen.send(2) gen.next() !def a_generator(): gen = nested_generator() to_yield = gen.next() while True: to_send = yield to_yield if to_send is None: to_yield = gen.next() else: to_yield = gen.send(to_send)

Danger! Untested

probably incorrect code!

Page 19: Python meetup: coroutines, event loops, and non-blocking I/O

next() send() throw() close()_i = iter(EXPR) try: _y = next(_i) except StopIteration as _e: _r = _e.value else: while 1: try: _s = yield _y except GeneratorExit as _e: try: _m = _i.close except AttributeError: pass else: _m() raise _e except BaseException as _e: _x = sys.exc_info() try: _m = _i.throw except AttributeError: raise _e else: try: _y = _m(*_x) except StopIteration as _e: _r = _e.value break else: try: if _s is None: _y = next(_i) else: _y = _i.send(_s) except StopIteration as _e: _r = _e.value break RESULT = _r

RESULT = yield from EXPR

def a_generator(): yield from nested_generator() def nested_generator(): yield from deeper_nested_generator() def deeper_nested_generator(): event_loop.register(self, for_io_op='recv', on_socket=a_socket) yield return a_socket.recv()

Page 20: Python meetup: coroutines, event loops, and non-blocking I/O

asyncio

❖ yield from

❖ its own low-level I/O operations (socket recv &c.)

❖ futures, promises, timers, …

❖ networking protocols

Page 21: Python meetup: coroutines, event loops, and non-blocking I/O

geventdef a_generator(): yield from nested_generator() def nested_generator(): yield from deeper_nested_generator() def deeper_nested_generator(): event_loop.register(self, for_io_op='recv', on_socket=a_socket) yield return a_socket.recv()

from gevent import monkey; monkey.patch_all() !def a_function(): nested_function() def nested_function(): deeper_nested_function() def deeper_nested_function(): return a_socket.recv() # monkey-patched! !import gevent jobs = [gevent.spawn(a_function) for _ in range(5)] gevent.wait(jobs)

how?!

Page 22: Python meetup: coroutines, event loops, and non-blocking I/O

greenlets

❖ full coroutines

❖ monkey-patched primitives can “yield to” the event loop directly

❖ C extension for CPython

Page 23: Python meetup: coroutines, event loops, and non-blocking I/O

node.js

really?

Page 24: Python meetup: coroutines, event loops, and non-blocking I/O

the summariesmodules tech buzzwords

Page 25: Python meetup: coroutines, event loops, and non-blocking I/O

module summary

twisted ❖ venerable ❖ focus on networking protocols ❖ perceived as large and complex; porting not easy gevent ❖ near “drop-in” in synchronous code ❖ python 3 support is … coming? asyncio ❖ python 3 stdlib

❖ (“Tulip” is a python 2 port: “yield From(a_generator)”) ❖ aims to be both low-level and protocol-level library

Page 26: Python meetup: coroutines, event loops, and non-blocking I/O

tech summary

greenlets ❖ full coroutines ❖ CPython hack; some pypy support yield from ❖ python 3 ❖ nested generators ❖ goldilocks solution? callbacks ❖ low-tech, no special support needed ❖ promises, futures, etc: there is space for useful abstraction ❖ node.js

Page 27: Python meetup: coroutines, event loops, and non-blocking I/O

buzzwords

❖ non-blocking I/O: yield the CPU instead of waiting

❖ an event loop gives it back to you when what you’re waiting for happens

❖ coroutines and generators let you write synchronous-style functions and still yield to the event loop mid-way

Page 28: Python meetup: coroutines, event loops, and non-blocking I/O

that’s all folksand yes we’re hiring

Page 29: Python meetup: coroutines, event loops, and non-blocking I/O

thank you’s and references

❖ y’all for your attention

❖ @saghul for getting me started on this whole business

❖ Peter Portante for a 2011 PyCon talk on coroutines

❖ Reinout & Maurits for PyGrunn summaries

❖ PEPs:

❖ 3156 (asyncio)

❖ 380 (yield from)