Talk given at DomCode meetup in Utrecht (August 2014) on different frameworks to do asynchronous I/O y Python, with a strong focus on asyncio (PEP-3156).
6. import socket
import socket
!
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.bind(('127.0.0.1', 1234))
server.listen(511)
print("Server listening on: {}".format(server.getsockname()))
!
client, addr = server.accept()
print("Client connected: {}".format(addr))
!
while True:
data = client.recv(4096)
if not data:
print("Client has disconnected")
break
client.send(data)
!
server.close()
7. except Exception:
• We can only handle one client at a time!
• Solutions for handling multiple clients
• Threads
• I/O multiplexing
• Check the C10K problem if you haven’t
already!
• http://www.kegel.com/c10k.html
8. try: sockets + threads
import socket
import thread
!
def handle_client(client, addr):
print("Client connected: {}".format(addr))
while True:
data = client.recv(4096)
if not data:
print("Client has disconnected")
break
client.send(data.upper())
!
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.bind(('127.0.0.1', 1234))
server.listen(511)
print("Server listening on: {}".format(server.getsockname()))
!
while True:
client, addr = server.accept()
thread.start_new_thread(handle_client, (client, addr))
9. except Exception:
• Threads have overhead
• Stack size
• Context switching
• Synchronisation
• GIL?
• Not for I/O!
10. I/O multiplexing
• Examine and block for I/O in multiple
file descriptors at the same time
• Single thread
• A file descriptor is ready if the
corresponding I/O operation can be
performed without blocking
• File descriptor has to be set to be
non-blocking
11. I/O is hard
• Different paradigms in Unix vs
Windows
• “are you ready?” vs “call me later”
• Frameworks
• Abstract platform differences
• Provide tools for easier development of
applications
15. Frameworks
• Platform abstraction
• Protocol implementations
• Integration with other event loops: Qt,
GLib, ...
• Different API styles
• Async file i/o?
16. import twisted
• Uses select, poll, kqueue, epoll from
the select module
• IOCP on Windows
• Integration with other event loops
• Factory / Protocol / Transport
abstractions
• Deferreds
17. import tornado
• Uses select, poll, kqueue, epoll from
the select module
• select() on Windows :-(
• Mainly oriented to web development
• Synchronous looking API with
coroutines / Futures
18. import gevent
• Uses libev for platform abstraction
• select() on Windows :-(
• Syncronous API using greenlet
• ME LIKEY! :-)
22. import asyncio
• Reference implementation for
PEP-3156
• Basic components for doing async i/o
• Works on Python >= 3.3
• Trollius: backport for Python >= 2.6
23. Goals
• Modern implementation of asynchronous
i/o for Python
• Use yield from (PEP-380)
• But don’t force it
• Don’t use anything that requires
Python > 3.3
• Interoperability with other frameworks
24. Goals
• Unix and Windows support
• IPv4 and IPv6
• TCP, UDP and pipes
• Basic SSL (secure by default)
• Subprocesses
25. Non goals
• Perfection
• Replacing current frameworks
• Protocol implementations
• Replace httplib, smtplib, ...
• Make it work on Python < 3.3
31. Event loop & policy
• Chooses the best i/o mechanism for a
given platform
• APIs for creating server and client
connections (TCP, UDP, …)
• Establish the context for a loop
32. Working with threads
• loop.call_soon_threadsafe(func, *args)
• loop.run_in_executor(exc, func, *args)
• loop.set_default_executor(exc)
!
• PEP-3148 executor
34. Coroutines, Futures & Tasks
• Coroutines
• a generator function, can receive values
• decorated with @coroutine
• Future
• promise of a result or an error
• Task
• Future which runs a coroutine
35. Coroutines & yield from
import asyncio
import socket
!
loop = asyncio.get_event_loop()
!
@asyncio.coroutine
def handle_client(client, addr):
print("Client connected: {}".format(addr))
while True:
data = yield from loop.sock_recv(client, 4096)
if not data:
print("Client has disconnected")
break
client.send(data)
!
@asyncio.coroutine
def accept_connections(server_socket):
while True:
client, addr = yield from loop.sock_accept(server_socket)
asyncio.async(handle_client(client, addr))
!
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.bind(('127.0.0.1', 1234))
server.listen(511)
server.setblocking(False)
print("Server listening on: {}".format(server.getsockname()))
!
loop.run_until_complete(accept_connections(server))
36. Coroutines & yield from
• Imagine the yield from is not there
• Imagine the code is executed
sequentially
• Not exactly the formal definition of yield
from (from PEP-380)
37. Futures
• Similar to Futures from PEP-3148
• API (almost) identical
• Adapted for asyncio’s single threaded
model
38. Futures + Coroutines
• yield from works with Futures!
• f = Future()
• Someone will set the result or exception
• r = yield from f
• Waits until done and returns f.result()
• Usually returned by functions
39. Undoing callbacks (I)
@asyncio.coroutine
def sync_looking_function(*args):
fut = asyncio.Future()
def cb(result, error):
if error is not None:
fut.set_result(result)
else:
fut.set_exception(Exception(error))
async_function(cb, *args)
return (yield from fut)
40. Undoing callbacks (II)
!
def sync_looking_function(*args):
fut = asyncio.Future()
def cb(result, error):
if error is not None:
fut.set_result(result)
else:
fut.set_exception(Exception(error))
async_function(cb, *args)
return fut
41. Tasks
• Unicorns covered in fairy dust
• It’s a coroutine wrapped in a Future
• Inherits from Future
• Works with yield from
• r = yield from Task(coro(...))
42. Tasks vs coroutines
• A coroutine doesn’t “advance” without
a scheduling mechanism
• Tasks can advance in their own
• The event loop is the scheduler!
43. Example
import asyncio
!
loop = asyncio.get_event_loop()
clients = {} # task -> (reader, writer)
!
def accept_client(client_reader, client_writer):
task = asyncio.Task(handle_client(client_reader, client_writer))
clients[task] = (client_reader, client_writer)
!
def client_done(task):
del clients[task]
!
task.add_done_callback(client_done)
!
@asyncio.coroutine
def handle_client(client_reader, client_writer):
while True:
data = (yield from client_reader.readline())
client_writer.write(data)
!!
f = asyncio.start_server(accept_client, '127.0.0.1', 12345)
server = loop.run_until_complete(f)
loop.run_forever()
45. Transports & Protocols
• Transport: represents a connection
(socket, pipe, ...)
• Protocol: represents an application
(HTTP server, IRC client, ...)
• They always go together
• API is based on function calls and
callbacks
46. Streams
• Abstraction on top of transports /
protocols
• File-like API using coroutines
47. Transport -> Protocol
• connection_made(transport)
• data_received(data)
• eof_received()
• connection_lost(exc)
!
• UDP, pipes and subprocesses are slightly
different
49. Enter Trollius!
• Backport of asyncio for Python >= 2.6
• By Victor Stinner (@haypo)
• Slightly different syntax
• yield instead of yield from
• raise Return(x) instead of return x on
generators
• pip install trollius
50. That was all?!
• Go read PEP-3156
• Implement a simple protocol (an IRC
client for example)
• Checkout the links on the last slide
• Use asyncio in your next project