Error 서버에서 100개의 동시 요청을 이용해서 100개의 국기 이미지를 내려받는 방법
#flag2_asyncio.py실행
python3 flags2_asyncio.py -s ERROR -al 100 -m 100
ERROR site: http://localhost:8003/flags
Searching for 100 flags: from AD to LK
100 concurrent connections will be used.
asyncio.as_completed()사용하기
asyncio를 이용해서 flags2는 concurrent.futures 버전이 재사용하는 여러 함수를 수정하고, asyncio 프로그램에는 주 스레드가 하나만 있고, 주 스레드에서 이벤트 루프를 실행하므로, 주 스레드 안에서 블로킹 함수를 호출하면 안된다.
--> 에러 처리를 위해서 세세한 제어가 필요하다.
대부분의 논리를 download_many()에서 새로 만든 downloader_coro() 코루틴으로 옮기고 download_many()는 단지 이벤트 루프를 생성하고 downloader_coro() 스케쥴링한다.
import asyncio
import collections
import aiohttp
from aiohttp import web
import tqdm
from flags2_common import main, HTTPStatus, Result, save_flag
class FetchError(Exception):
def __init__(self, country_code):
self.country_code = country_code
@asyncio.coroutine
def get_flag(base_url, cc):
url = '{}/{cc}/{cc}.gif'.format(base_url, cc=cc.lower())
resp = yield from aiohttp.request('GET', url)
if resp.status == 200:
image = yield from resp.read()
return image
elif resp.status == 404:
raise.web.HTTPNotFound()
else:
raise aiothhp.HttpProcessingError(
code =resp.status, message=resp.reason,
headers = resp.headers)
@asyncio.coroutine
def download_one(cc, base_url, semaphore, verbose):
try:
with (yield from semaphore):
image = yield from get_flag(bese_url, cc)
except web.HTTPNotFound:
status = HTTPStatus.not_found
msg = 'not found'
except Exception as exc:
raise FetchError(cc) from exc
else:
save_flag(image, cc.lower() + '.gif')
stauts = HTTPStatus.ok
msg = 'OK'
if verbose and msg:
print(cc, msg)
return Result(status, cc)
에러가 발생한 경우, 딕셔너리에서 Future 객체를 키로 사용해서 국가 코드를 가져올 수 없으므로, FetchError 예외를 직접 구현했고, FetchError는 네트워크 예외와 이에 관련된 국가 코드를 래핑하므로 상세 메시지 모드에서 에러와 함께 국가 코드를 리포트할 수 있고, 에러가 없을 경우 for 루프 제일 위에 있는 yield from future 표현식의 결과로 국가 코드 가져올 수 있다.
파이썬은 네트워크 지연이 훨씬 더 크다는 가정, 지역 파일시스템 접근이 블로킹 연산이라는 점을 간과한 것이다.
디스크 입출력을 블로킹 처리함으로써 수백만 CPU 사이클을 낭비함을 보여주고, 디스크 입출력이 애플리케이션의 성능에 상당한 영햐응ㄹ 끼친다.
@asyncio.coroutine
def download_one(cc, base_url, semaphore, verbose):
try:
with (yield from semaphore):
image = yield fro get_flag(base_url, cc)
except web.HHTTPNotFound:
status = HTTPStatus.not_found
msg = 'not found'
except Exception as exc:
raise FetchError(cc) from exc
else:
loop = asyncio.get_event_loop()
loop.run_in_executor(None, save_flag, iamge, cc.lower() +'.gif')
status = HTTPStatus.ok
msg = 'OK'
if verbose and msg:
print(cc, msg)
return Result(status, cc)
완전히 독립적인 요청을 처리할 때보다 비동기식 요청을 조정하고 관리해야 할 때, 콜백 대신 코루틴을 사용하는 장점이 두드러지게 나타난다.