본문 바로가기
Pyhton

asyncio, aiohttp 체험기

by Django_ 2021. 7. 16.
반응형

일반적으로 프로그램은 동기적으로 동작을 합니다.

동기적이라 하면 하나의 작업이 완료될때까지 다음 작업이 실행되지 않는 것을 의미합니다.

 

예를들어 주소의 지번, 도로명 주소와 영문 주소를 얻는 API를 동작 시킨다고 가정해보겠습니다.

행정안전부에서 주소 키워드로 검색시 결과를 돌려주는 API를 제공해주는데요

하지만 지번, 도로명 주소 검색 따로 영문 주소 따로 호출해야합니다.

이때 동기적으로 수행을 한다고 했을때 아래와 같이 작성할 수 있습니다.

# 간단한 예시
# 시안 성을 위해 불필요한 정보는 제거됨

def address_ko(keyword):
    print(f"address_ko 시작")
    url = "https://www.juso.go.kr/addrlink/addrLinkApi.do"
    data = requests.get(ur, params={'keyword':keyword}).json()
    print(f"address_ko 종료")
    
def address_en(keyword):
    print(f"address_en 시작")
    url = "https://www.juso.go.kr/addrlink/addrEngApi.do"
    data = requests.get(url, params={'keyword':keyword}).json()
    print(f"address_en 종료")

def call_all(keyword):
    start = datetime.now()
    address_ko(keyword)
    address_en(keyword)
    print(datetime.now()-start)
    
call_all('중구 세종대로 110')

편한 설명을 위해 지번과 도로명 주소를 한글 주소라고 칭하겠습니다.

한글 주소 조회를 시작하고 한글 주소 조회가 마무리 되고 영문 주소 조회를 시작하고 영문 주소 조회를 종료하게 됩니다.

약 0.41 초 가량 소요된것이기 때문에 빠른 속도라고 생각하실수 있지만 만약 사이트가 많아지고 

사이트별로 레이턴시가 발생된다면 더욱 많은 시간이 소요될것입니다.

이러한 상황에서 사용자가 완료된 결과를 기다려야한다면 사용자는 좋지 않은 경험을 기억하게 될것입니다.

 

이때 필요한것이 비동기입니다~! 

파이썬에서는 asyncio를 제공해주게 됩니다. ( python 3.7 이상 사용을 권장드립니다.)

request의 경우 asyncio만으로 비동기 처리되지 않고 동기 처리되니 꼭 주의하셔야합니다.

저는 request 비동기 처리를 위해 aiohttp를 사용하였습니다.

# 간단한 예시
# 시안 성을 위해 불필요한 정보는 제거됨

import asyncio
import aiohttp

async def address_ko(keyword):
    print(f"address_ko 시작")
    url = "https://www.juso.go.kr/addrlink/addrLinkApi.do"
    async with aiohttp.ClientSession().get(url, params={"keyword":keyword}) as new_resp:
        soup = await new_resp.text()
    print(f"address_ko 끝")


async def address_en(keyword):
    print(f"address_en 시작")
    url = "https://www.juso.go.kr/addrlink/addrEngApi.do"
    async with aiohttp.ClientSession().get(url, params={"keyword":keyword}) as new_resp:
        soup = await new_resp.text()
    print(f"address_en 끝")


async def address_main(keyword):
    start = datetime.now()
    await asyncio.gather(address_ko(keyword), address_en(keyword))
    print(datetime.now()-start)


#  asyncio.run() python 3.7부터 지원
asyncio.run(address_main('중구 세종대로 110'))

시작은 한글 주소 조회가 호출되고 한글 주소 호출이 끝나기도 전에 영문 주소 조회가 호출 된것을 확인 할 수 있습니다.

그리고 한글 주소 조회가 끝나기도 전에 영문 주소호출이 끝이 났네요~

이렇게 호출함에 있어 동기적으로 대기하지 않고 병렬적으로 실행이 되게 됩니다. 

시간을 보면 0.093 초의 수행시간이 소요되었네요

서버 자원도 효율적으로 사용하고 시간도 단축 되는것을 경험할 수 있습니다.

하지만 이 경우에도 사용자의 대기 시간은 존재한다는 점을 유의해야합니다.

만약 작업량이 많아 대기 시간이 더욱 오래 걸리는 경우라면

트리거만 발생시켜 호출하는 서버리스를 고민해보면 좋을것 같다는 생각이 들었습니다.

(아직 구현은 안해봤지만 조만간 구현하면서 업로드하겠습니다!)

 

이번에 이 작업을 진행하면서 알게 된것들도 공유합니다.

 

1. RuntimeError: Event loop is closed (윈도우 사용자)

정삭적으로 작동을 하는데 "RuntimeError: Event loop is closed" 에러가 발생하였습니다.

Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x03695D18>
Traceback (most recent call last):
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\lib\asyncio\proactor_events.py", line 116, in __del__
    self.close()
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\lib\asyncio\proactor_events.py", line 108, in close
    self._loop.call_soon(self._call_connection_lost, None)
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\lib\asyncio\base_events.py", line 719, in call_soon
    self._check_closed()
  File "C:\Users\user\AppData\Local\Programs\Python\Python38-32\lib\asyncio\base_events.py", line 508, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed

아래 와 같이 한줄 추가하면 해결이 됩니다.

asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # 추가
asyncio.run(address_main("중구 세종대로 110"))

 

2. jupyter notebook에서 실행되지 않는 asyncio.run()

asyncio.run(address_main("중구 세종대로 110"))  # asyncio.run 정상작동하지 않습니다.
>> await address_main("중구 세종대로 110")

 

반응형

댓글