My story of
Async IO
Arduino
Socket Server
import socket
TCP_IP = '127.0.0.1'
TCP_PORT = 5005
BUFFER_SIZE = 2048 # Normally 1024, but we want fast response
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(1)
while True:
conn, addr = s.accept()
print('Connection address:', addr)
while 1:
data = conn.recv(BUFFER_SIZE)
if not data: break
print("received data:", data)
conn.send(data) # echo
Because,
One client at a time
Let's add threads
import socket
import threading
TCP_IP = '127.0.0.1'
TCP_PORT = 5005
BUFFER_SIZE = 2048 # Normally 1024, but we want fast response
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(1)
def connection(conn):
data = conn.recv(BUFFER_SIZE)
if not data: return
print "received data:", data
conn.send(data) # echo
while True:
conn, addr = s.accept()
print 'Connection address:', addr
while 1:
t = threading.Thread(target=connection, args=(conn,))
t.start()
break
Because,
- Threads are overhead
- Context Switching
- Synchronization
- GIL
Still
Threadpool
import socket
from multiprocessing.pool import ThreadPool
TCP_IP = '127.0.0.1'
TCP_PORT = 5005
BUFFER_SIZE = 2048 # Normally 1024, but we want fast response
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(1)
def connection(conn):
data = conn.recv(BUFFER_SIZE)
if not data: return
print "received data:", data
conn.send(data) # echo
thread_pool = ThreadPool(5)
while True:
conn, addr = s.accept()
print 'Connection address:', addr
while 1:
thread_pool.map(
connection, [conn]
)
break
Because,
- Threads are overhead
- Context Switching
- Synchronization
- GIL
Still
Scale sockets
Threadpool
processpool
GIL
processes
threads
os
pthreads
Event Loop
Node.js
When I heard about AsyncIO event loop
Hello world
import asyncio
async def say(what, when):
await asyncio.sleep(when)
print(what)
loop = asyncio.get_event_loop()
loop.run_until_complete(say('hello world', 1))
loop.close()
hello world
Output
Coroutine
Event loop
Hello World execution
import asyncio
async def say(what, when):
await asyncio.sleep(when)
print(what)
loop = asyncio.get_event_loop()
loop.run_until_complete(say('hello world', 1))
loop.close()
Hello world in Py 3.3
import asyncio
@asyncio.coroutine
def say(what, when):
yield from asyncio.sleep(when)
print(what)
loop = asyncio.get_event_loop()
loop.run_until_complete(say('hello world', 1))
loop.close()
hello world
Output
Coroutine
Event loop
def gen1():
yield "a"
yield "b"
yield "c"
def gen2():
for c in gen1():
yield c
yield "d"
yield "e"
yield "f"
print([i for i in gen2()])
Nested Yield
Yield from
def gen1():
yield "a"
yield "b"
yield "c"
def gen2():
yield from gen1()
yield "d"
yield "e"
yield "f"
return "RETURN VALUE"
def y_from():
a = yield from gen2()
print(a)
for i in y_from():
print(i)
Output
a
b
c
d
e
f
RETURN VALUE
for c in gen1():
yield c
Callbacks
import asyncio
def success(msg):
print("Success - %s" % msg)
async def callback_example(loop):
print('registering callbacks')
loop.call_soon(success, "Call soon")
loop.call_later(2, success, "Call later 2 sec")
loop.call_later(3, success, "Call later 3 sec")
loop.call_at(1506541660, success, "Network connect")
loop.time()
await asyncio.sleep(3)
loop = asyncio.get_event_loop()
loop.run_until_complete(callback_example(loop))
loop.close()
registering callbacks
Success - Call soon
Success - Call later 2 sec
Success - Call later 3 sec
Output
Callbacks with delay
import asyncio
def success(msg):
print("Success - %s" % msg)
async def callback_example(loop):
print('registering callbacks')
loop.call_soon(success, "Call soon")
loop.call_later(2, success, "Call later 2 sec")
loop.call_later(3, success, "Call later 3 sec")
loop.call_at(1506541660, success, "Network connect")
loop.time()
await asyncio.sleep(3)
loop = asyncio.get_event_loop()
loop.run_until_complete(callback_example(loop))
loop.close()
registering callbacks
Success - Call soon
Success - Call later 2 sec
Success - Call later 3 sec
Output
Execution callbacks
import asyncio
def success(msg):
print("Success - %s" % msg)
async def callback_example(loop):
print('registering callbacks')
loop.call_soon(success, "Call soon")
loop.call_later(2, success, "Call later 2 sec")
loop.call_later(3, success, "Call later 3 sec")
loop.call_at(1506541660, success, "Network connect")
loop.time()
await asyncio.sleep(3)
loop = asyncio.get_event_loop()
loop.run_until_complete(callback_example(loop))
loop.close()
Execution callbacks
import asyncio
def success(msg):
print("Success - %s" % msg)
async def callback_example(loop):
print('registering callbacks')
loop.call_soon(success, "Call soon")
loop.call_later(2, success, "Call later 2 sec")
loop.call_later(3, success, "Call later 3 sec")
loop.call_at(1506541660, success, "Network connect")
loop.time()
await asyncio.sleep(3)
loop = asyncio.get_event_loop()
loop.run_until_complete(callback_example(loop))
loop.close()
Future
import asyncio
async def slow_operation(future):
await asyncio.sleep(1)
future.set_result('Future is done!')
loop = asyncio.get_event_loop()
future = asyncio.Future()
asyncio.ensure_future(slow_operation(future))
loop.run_until_complete(future)
print(future.result())
loop.close()
Future is done!
Output
import asyncio
async def task_func():
print('in task_func')
return 'the result'
async def main(loop):
print('creating task')
task = loop.create_task(task_func())
print('waiting for {!r}'.format(task))
return_value = await task
print('task completed {!r}'.format(task))
print('return value: {!r}'.format(return_value))
event_loop = asyncio.get_event_loop()
try:
event_loop.run_until_complete(main(event_loop))
finally:
event_loop.close()
creating task
waiting for <Task pending coro=<task_func() running at tasks.py:4>>
in task_func
task completed <Task finished coro=<task_func() done, defined at tasks.py:4> result='the result'>
return value: 'the result'
Tasks
Executor
import asyncio
from concurrent.futures import ThreadPoolExecutor
print('running async test')
def say_boo():
for i in range(10):
print(i)
def say_baa():
for i in range(10):
print(i)
if __name__ == "__main__":
executor = ThreadPoolExecutor(2)
loop = asyncio.get_event_loop()
boo = asyncio.ensure_future(loop.run_in_executor(executor, say_boo))
baa = asyncio.ensure_future(loop.run_in_executor(executor, say_baa))
loop.run_forever()
Debug
Debug logs can be enabled by
- Setting PYTHONASYNCIODEBUG=1
- loop.set_debug(True)
logging.basicConfig(
level=logging.DEBUG,
format='%(levelname)7s: %(message)s',
stream=sys.stderr,
)
Let's write echo
server in asyncio
import asyncio
import logging
import sys
SERVER_ADDRESS = ('localhost', 5555)
logging.basicConfig(
level=logging.DEBUG,
format='%(name)s: %(message)s',
stream=sys.stderr,
)
log = logging.getLogger('main')
async def echo(reader, writer):
address = writer.get_extra_info('peername')
log = logging.getLogger('echo_{}_{}'.format(*address))
log.debug('connection accepted')
while True:
data = await reader.read(128)
if data:
log.debug('received {!r}'.format(data))
writer.write(data)
await writer.drain()
log.debug('sent {!r}'.format(data))
else:
log.debug('closing')
writer.close()
return
event_loop = asyncio.get_event_loop()
factory = asyncio.start_server(echo, *SERVER_ADDRESS)
server = event_loop.run_until_complete(factory)
log.debug('starting up on {} port {}'.format(*SERVER_ADDRESS))
try:
event_loop.run_forever()
except KeyboardInterrupt:
pass
finally:
log.debug('closing server')
server.close()
event_loop.run_until_complete(server.wait_closed())
log.debug('closing event loop')
event_loop.close()
Streams API
TCP Echo server with callbacks
import asyncio
import logging
import sys
SERVER_ADDRESS = ('localhost', 5555)
logging.basicConfig(
level=logging.DEBUG,
format='%(name)s: %(message)s',
stream=sys.stderr,
)
log = logging.getLogger('main')
class EchoServer(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
self.address = transport.get_extra_info('peername')
self.log = logging.getLogger(
'EchoServer_{}_{}'.format(*self.address)
)
self.log.debug('connection accepted')
def data_received(self, data):
self.log.debug('received {!r}'.format(data))
self.transport.write(data)
self.log.debug('sent {!r}'.format(data))
event_loop = asyncio.get_event_loop()
# Create the server and let the loop finish the coroutine before
# starting the real event loop.
factory = event_loop.create_server(EchoServer, *SERVER_ADDRESS)
server = event_loop.run_until_complete(factory)
log.debug('starting up on {} port {}'.format(*SERVER_ADDRESS))
# Enter the event loop permanently to handle all connections.
try:
event_loop.run_forever()
finally:
log.debug('closing server')
server.close()
event_loop.run_until_complete(server.wait_closed())
log.debug('closing event loop')
event_loop.close()
Synchronisation
- Locks
- Events
- Condition
import asyncio
import functools
def unlock(lock):
print('callback releasing lock')
lock.release()
async def coro1(lock):
print('coro1 waiting for the lock')
with await lock:
print('coro1 acquired lock')
print('coro1 released lock')
async def coro2(lock):
print('coro2 waiting for the lock')
await lock
try:
print('coro2 acquired lock')
finally:
print('coro2 released lock')
lock.release()
async def main(loop):
lock = asyncio.Lock()
print('acquiring the lock before starting coroutines')
await lock.acquire()
print('lock acquired: {}'.format(lock.locked()))
loop.call_later(2, functools.partial(unlock, lock))
print('waiting for coroutines')
await asyncio.wait([coro1(lock), coro2(lock)]),
event_loop = asyncio.get_event_loop()
try:
event_loop.run_until_complete(main(event_loop))
finally:
event_loop.close()
acquiring the lock before starting coroutines
lock acquired: True
waiting for coroutines
coro1 waiting for the lock
coro2 waiting for the lock
callback releasing lock
coro1 acquired lock
coro1 released lock
coro2 acquired lock
coro2 released lock
Locks
Events
import asyncio
import functools
def set_event(event):
print('setting event in callback')
event.set()
async def coro1(event):
print('coro1 waiting for event')
await event.wait()
print('coro1 triggered')
async def coro2(event):
print('coro2 waiting for event')
await event.wait()
print('coro2 triggered')
async def main(loop):
# Create a shared event
event = asyncio.Event()
print('event start state: {}'.format(event.is_set()))
loop.call_later(
2, functools.partial(set_event, event)
)
await asyncio.wait([coro1(event), coro2(event)])
print('event end state: {}'.format(event.is_set()))
event_loop = asyncio.get_event_loop()
try:
event_loop.run_until_complete(main(event_loop))
finally:
event_loop.close()
event start state: False
coro1 waiting for event
coro2 waiting for event
setting event in callback
coro1 triggered
coro2 triggered
event end state: True
Condition
import asyncio
async def consumer(condition, n):
with await condition:
print('consumer {} is waiting'.format(n))
await condition.wait()
print('consumer {} triggered'.format(n))
print('ending consumer {}'.format(n))
async def manipulate_condition(condition):
print('starting manipulate_condition')
await asyncio.sleep(0.1)
for i in range(1, 3):
with await condition:
print('notifying {} consumers'.format(i))
condition.notify(n=i)
await asyncio.sleep(0.1)
with await condition:
print('notifying remaining consumers')
condition.notify_all()
print('ending manipulate_condition')
async def main(loop):
# Create a condition
condition = asyncio.Condition()
consumers = [
consumer(condition, i)
for i in range(5)
]
loop.create_task(manipulate_condition(condition))
await asyncio.wait(consumers)
event_loop = asyncio.get_event_loop()
try:
result = event_loop.run_until_complete(main(event_loop))
finally:
event_loop.close()
starting manipulate_condition
consumer 4 is waiting
consumer 1 is waiting
consumer 0 is waiting
consumer 2 is waiting
consumer 3 is waiting
notifying 1 consumers
consumer 4 triggered
ending consumer 4
notifying 2 consumers
consumer 1 triggered
ending consumer 1
consumer 0 triggered
ending consumer 0
notifying remaining consumers
ending manipulate_condition
consumer 2 triggered
ending consumer 2
consumer 3 triggered
ending consumer 3
Queue
Some examples
Best practices
- Use Locks, Events and Condition
- Avoid modification of global veriables
- Consider GIL
- Use async libraries
- Avoid recursions
Async IO
By Hitul Mistry
Async IO
- 725