20160519

Python 3.5 async/await 맛보기

Python 3.5의 async/await 문법 구경중. 그동안 3.4의 @asyncio.coroutine + yield from 로 했던거랑 비교해서 훨씬 문법적으로 깔끔해졌다.

기본 예:

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as resp:
        return await resp.text()

async def main():
    urls = ['http://a.example.com', 'http://b.example.com', 'http://c.example.com']
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, u) for u in urls]
        results = await asyncio.gather(*tasks)
        for r in results:
            print(len(r))

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

외부 API 여러 곳에 병렬로 요청 보낼 때 이만한게 없음. gather로 묶으면 3개가 대충 동시에 나감.

그런데 진짜 "async 컨텍스트로 안 들어갈 수 있으면 안 들어가는게 낫다" 는 감상도 같이 듦. 이유:

  • 블로킹 I/O (일반 requests.get 등) 한 번이라도 섞이면 이벤트 루프가 막힘. 생태계 전체가 async-ready여야 의미 있음
  • DB 드라이버도 aiomysql, asyncpg 같은 async 전용 드라이버 써야함. SQLAlchemy ORM은 아직 async 지원 약함
  • 디버깅이 동기 코드보다 훨씬 어려움. 스택트레이스가 불친절
  • threading으로도 충분한 케이스 많음 (I/O 적당히 분산되는 경우)

그래서 지금은 크롤링/외부 API 집계처럼 순수 I/O bound에 async를 쓰고, 일반 웹서비스는 여전히 WSGI + threading 기반. 나중에 sanic 같은 async 웹프레임워크가 무르익으면 그때 다시 검토.

3.5 자체는 LTS는 아닌데 2020년까지 유지보수 예정이라 당분간 3.5 기준으로 써도 될듯. 회사 프로젝트 하나 신규 건을 3.5로 시작해볼까 고민중.