#!/usr/bin/env python # coding: utf-8 # In[9]: import asyncio import aiohttp loop = asyncio.get_event_loop() presenter = {'name': 'Mantas Zimnickas'} # In[1]: import asyncio # In[66]: print(presenter) # # Concurrent vs Parallel vs Asynchronous # - **Concurrency** is about *dealing with* lots of things at once [1]. # - Possible ways to deal with *concurrency*: # - Parallelisation. # - Asynchronous IO. # # Parallelisation # - **Parallelisation** is about *doing* lots of things at once[1]. # - **Parallelisation** helps to solve CPU-bound performance issues. # - Parallelisation tools in Python's standard library: # # ```python # import multiprocessing # import threading # import concurrent.futures # ``` # # Asynchronous IO # - **Asynchronous IO** is about *doing* IO processing in a non-blocking way [2]. # - **Asynchronous IO** helps to solve IO bound performance issues. # - Asynchornous IO tools in Python's standart library: # # ```python # import asyncio # ``` # # What is IO-bound? # # Below is a list of possible reasons for IO-bound performance issues: # # * Reading/writing to a file. # * Sending/receiving data over the network. # * Executing database queries. # * Executing processes and comunicating with them over pipes. # * Waiting for user input from `sys.stdin`. # * Writing output to `sys.stdout` or `sys.stderr`. # # History of asynchronous IO in python # # * 2001 - PEP 255: Simple generators (`yield` statement) # * 2002 - Twisted # * 2009 - Gevent # * 2010 - Tornado # * 2010 - PEP 3153: Asynchronous IO support # * 2012 - PEP 3156: Asynchronous IO Support Rebooted: the "asyncio" Module # * 2014 - Tulip # * 2014 - asyncio # # Two types of asyncio users # # * Those who use high level asyncio coroutines. # * Those who create new coroutines using low level asyncio primitives. # # Callbacks # In[8]: from functools import partial from concurrent.futures import ThreadPoolExecutor def add(a, b): return a + b def done(a, b, future): print('%s + %s = %s' % (a, b, future.result())) with ThreadPoolExecutor(max_workers=1) as pool: future = pool.submit(add, 2, 2) future.add_done_callback(partial(done, 2, 2)) # # Using coroutines # In[3]: def add(a, b): return a + b def main(): print('2 + 2 =', add(2, 2)) main() # In[4]: @asyncio.coroutine def add(a, b): return a + b @asyncio.coroutine def main(): print('2 + 2 =', (yield from add(2, 2))) loop.run_until_complete(main()) # # Using coroutines: aiohttp # In[10]: import aiohttp @asyncio.coroutine def get_size(url): response = yield from aiohttp.request('GET', url) return len((yield from response.read())) @asyncio.coroutine def main(): size = yield from get_size('https://python.org/') print('python.org size is:', size) loop.run_until_complete(main()) # * This example works much faster without `asyncio`. # * `asyncio` shines with many concurent input/output. # # Using coroutines: aiohttp # In[23]: @asyncio.coroutine def get_size(semaphore, url): with (yield from semaphore): response = yield from aiohttp.request('GET', url) return len((yield from response.read())) @asyncio.coroutine def main(urls): semaphore = asyncio.Semaphore(3) sizes = [get_size(semaphore, url) for url in urls] return [(yield from size) for size in asyncio.as_completed(sizes)] urls = ['https://python.org/'] loop.run_until_complete(main(urls)) # # Using coroutines: aiohttp.web # In[5]: from aiohttp import web @asyncio.coroutine def handle(request): return web.Response(body=b'Hello world.') @asyncio.coroutine def init(loop): app = web.Application(loop=loop) app.router.add_route('GET', '/', handle) return (yield from loop.create_server(app.make_handler(), '127.0.0.1', 8080)) loop.run_until_complete(init(loop)) # # Generator # In[131]: def generator(): yield 1 yield 2 return 3 def wrapper(): yield (yield from generator()) for x in wrapper(): print(x) # # Coroutine # # * In python a coroutine is same thing as generator. # * Coroutine is a function with multiple entry points for suspending and resuming execution at certain locations [4]. # # Coroutine # In[123]: def coroutine(): print('b:', (yield 1)) print('b:', (yield 3)) print('b:', (yield 5)) # In[124]: a = coroutine() # In[125]: print('a:', next(a)) # In[126]: print('a:', a.send(2)) # In[127]: print('a:', a.send(4)) # # asyncio coroutine # In[17]: @asyncio.coroutine def my_coroutine(): print('hello') # In[18]: type(my_coroutine) # In[19]: type(my_coroutine()) # In[21]: asyncio.iscoroutinefunction(my_coroutine), asyncio.iscoroutine(my_coroutine()) # # Future # # * A class used to represent a result that may not be available yet [3]. # * Also know as `deffered` from Twisted or `promise` in other libraries. # In[51]: future = asyncio.futures.Future() future.add_done_callback(lambda future: print("Future's done callback here.")) # In[52]: future.set_result("My result") # In[53]: future.done() # In[54]: future.exception() # In[55]: future.result() # # Low level coroutines # In[29]: busy, ready = 0, 1 responses = [busy, busy, ready] def callback(future, n): if responses.pop(0) == ready: future.set_result(n) else: loop.call_later(1, callback, future, n+1) def coroutine(): future = asyncio.futures.Future() callback(future, 1) return future loop.run_until_complete(coroutine()) # # References # # - [1] Andrew Gerrand. [Concurrency is not parallelism](http://blog.golang.org/concurrency-is-not-parallelism). The Go Blog. 16 January 2013. # - [2] [Asynchronous I/O](http://en.wikipedia.org/wiki/Asynchronous_I/O). Wikipedia. # - [3] Nicholas H. Tollervey. [Asynchronous Python](http://ntoll.org/article/asyncio). Personal blog. 28 April 2014. # - [4] [Coroutine](http://en.wikipedia.org/wiki/Coroutine). Wikipedia.