asyncio 내려받기 스크립트 개선

매일 공부(ML)·2023년 5월 26일
0

Fluent Python

목록 보기
128/130

제어 흐름

asyncio를 이용한 동시성

asyncio 내려받기 스크립트 개선

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 표현식의 결과로 국가 코드 가져올 수 있다.


Executor를 이용해서 이벤트 루프 블로킹 피하기

파이썬은 네트워크 지연이 훨씬 더 크다는 가정, 지역 파일시스템 접근이 블로킹 연산이라는 점을 간과한 것이다.

디스크 입출력을 블로킹 처리함으로써 수백만 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)

완전히 독립적인 요청을 처리할 때보다 비동기식 요청을 조정하고 관리해야 할 때, 콜백 대신 코루틴을 사용하는 장점이 두드러지게 나타난다.

profile
성장을 도울 아카이빙 블로그

0개의 댓글