python asyncio
는 비동기 프로그래밍을 위해 python 3.4 버전에 추가된 내장 라이브러리이다. 그 이후로 계속 변화를 거치면서 사용법이 조금씩 달라졌는데, python 3.7을 기준으로 테스트해보았다.
0. Asyncio?, Aiohttp?
1) Asyncio
파이썬에서의 비동기 프로그래밍을 위한 asyncio
라이브러리는 HummingLab님의 글이나 DaleSeo님의 글에 매우 잘 설명이 되어 있다. 하나의 thread로 여러 요청에 응답하는 I/O bound한 작업에 적합하다.
2) Aiohttp
aiohttp
는 asyncio
를 위한 http 서버/클라이언트 요청을 수행하는 라이브러리이다. 파이썬 request
가 동기로 요청을 처리하기 때문에 비동기로 http 요청을 처리하기 위해서는 aiohttp
가 필요하다.
1. API 호출 테스트
API 호출은 jsonplaceholder 사이트의 테스트 api를 사용하였으며 주피터 환경에서 테스트하였다. 파이썬 파일에서의 실행 방식과는 조금 다른데 글 하단에 설명해두었다.
1) Asyncio
API호출 코드가 다음과 같이 있다고 하자.
import requests
async def api_1(number):
res = requests.get(f'https://jsonplaceholder.typicode.com/posts/{number}')
return res.json()
async def api_2(number):
res = requests.get(f'https://jsonplaceholder.typicode.com/comments/{number}')
return res.json()
이때 asyncio.create_task()
또는 asyncio.gather()
두 가지 방법으로 실행시킬 수 있다.
import requests
import asyncio
async def main(number):
task1 = asyncio.create_task(api_1(number))
task2 = asyncio.create_task(api_2(number))
result_of_task1 = await task1
result_of_task2 = await task2
return result_of_task1, result_of_task2
# 주피터에서 실행 시
result_1, result_2 = await main(number)
# 파이썬 스크립트로 실행 시
result_1, result_2 = asyncio.run(main(query))
print(result_1, result_2)
>>>> {'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'}
{'postId': 1, 'id': 1, 'name': 'id labore ex et quam laborum', 'email': 'Eliseo@gardner.biz', 'body': 'laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium'}
async def main(number):
result = await asyncio.gather(*[api_1(number), api_2(number)])
return result
# 주피터로 실행 시
result_1, result_2 = await main(number)
# 파이썬 스크립트로 실행 시
result_1, result_2 = asyncio.run(main(query))
>>>> {'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'}
{'postId': 1, 'id': 1, 'name': 'id labore ex et quam laborum', 'email': 'Eliseo@gardner.biz', 'body': 'laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium'}
2) Requests & Asyncio+Requests & Aiohttp 호출시간 비교
import aiohttp
import requests
import asyncio
request_numbers = list(range(100))
### 1) Requests만 사용
s = time.time()
for number in request_numbers:
res = requests.get(f'https://jsonplaceholder.typicode.com/posts/{number}')
print(time.time()-s)
>>>> 17.55
### 2) Asyncio+Requests 사용
async def get_using_requests(number):
res = requests.get(f'https://jsonplaceholder.typicode.com/posts/{number}')
return res.json()
async def main(numbers):
result = await asyncio.gather(
*[get_using_requests(number) for number in numbers]
)
return result
s = time.time()
results = await main(request_numbers)
print(time.time()-s)
>>>> 17.62
### 3) Aiohttp 사용
async def get_using_aiohttp(session, number):
async with session.get(f'https://jsonplaceholder.typicode.com/posts/{number}') as response:
res = await response.json()
return res
async def main(numbers):
async with aiohttp.ClientSession() as session:
results = await asyncio.gather(
*[get_using_aiohttp(session, number) for number in numbers]
)
return results
s = time.time()
results = await main(request_numbers)
print(time.time()-s)
>>>> 0.76
Aiohttp
를 사용했을 때가 시간이 제일 적게 걸리는 것을 확인할 수 있다. 하지만 서버 매우 부담을 줄 수 있으니,, 참고만 하면 될 것 같다.
3) Asyncio 주피터로 실행 & CLI로 실행
stackoverflow글에 따르면 asyncio.run()
은 다른 event loop이 이미 실행중일 때 실행될 수 없다고 한다. 그런데 IPython>=7.0 이상의 주피터는 이미 event loop을 실행시키고 있다. 때문에 주피터에서 asyncio.run()
을 실행 시 "RuntimeError: asyncio.run() cannot be called from a running event loop"와 같은 에러가 뜨며, await
으로 감싼 함수를 실행시켜야 한다.
Jupyter / Ipython
async def main():
print(1)
await main()
Python (3.7)
import asyncio
async def main():
print(1)
asyncio.run(main())
'python 메모' 카테고리의 다른 글
[예외처리] try, except, finally (0) | 2022.07.18 |
---|---|
[transformers] tokenizer 결과 (0) | 2022.04.04 |
[python] ProcessPoolExecutor로 분할+병렬 연산 (0) | 2022.01.17 |
[pandas] 특정 row들의 셀 병합하여 excel로 읽고 쓰기 (0) | 2021.11.19 |
[pandas] warning 메세지 출력 안하기 (0) | 2021.11.19 |