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

Post on 06-May-2015

683 Views

Category:

Engineering

6 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

Tikitu de Jager • @tTikitu • tikitu@buzzcapture.com

going asynccoroutines event loops non-blocking I/O

PUN • Utrecht • 20-6-2014

magicimport asyncio import asyncio_redis !@asyncio.coroutine 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

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

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

“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

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? …

event loopdid anything happen? how about now? callback hell

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)

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”

coroutinesstop/go generators yield

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

yield to the event loop

import asyncio import asyncio_redis !@asyncio.coroutine 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()

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)

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

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)

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)

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

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!

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

asyncio

❖ yield from

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

❖ futures, promises, timers, …

❖ networking protocols

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?!

greenlets

❖ full coroutines

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

❖ C extension for CPython

node.js

really?

the summariesmodules tech buzzwords

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

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

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

that’s all folksand yes we’re hiring

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)

top related