티스토리 뷰
안녕하세요. Teus입니다.
이번 포스팅은 Flask 2.0에서 지원되는 Async함수에 대해서 이야기 합니다.
1. Flask 2.0
아마 처음에 Flask의 비동기 함수를 보고
Flask에서 비동기함수라니, 뭔 멍멍이같은 소리여😒
하실 수 있습니다.
하지만, Flask가 2.0으로 버전업이 되면서 Async함수를 쓸 수 있게 되었습니다.
Flask Async Official Docs
하지만 Aysnc 함수를 만들 수 있다고 했지 ASGI를 지원한다고 하지 않았습니다.
아래 예제를 보겠습니다.
import asyncio
from flask import Flask
import threading
app = Flask(__name__)
async def async_sleep():
await asyncio.sleep(3)
return 'zzz'
@app.route("/data")
async def get_data():
print(threading.current_thread())
data = await async_sleep()
return data
@app.route("/data_sync")
def get_data_sync():
import time
print(threading.current_thread())
time.sleep(3)
return "zzz"
app.run(
host="127.0.0.1",
port = 8888,
processes = 1,
threaded = False
)
이제 두개의 process에서 requests를 보내보면, 아래와 같습니다.
werkzeug의 thread와 process를 모두 비활성화 시키고
비동기 sleep을 진행한 async router
와 sleep을 진행한 일반 router
모두
3초가 흐른 뒤, 다음 요청을 처리하는것을 알 수 있습니다.
🤔????
우리가 생각하는 비동기랑은 다르게 동작하는 것을 알 수 있습니다.
Aysnc함수를 사용하면 우리가 생각하기에는
- A의 요청이 들어오고, 이 요청을 처리다가 I/O작업을 마주함
- A는 I/O작업을 진행하느라 cpu가 놀고있음.
- 이때 B의 요청이 들어옴
- B의 요청을 처리함
- B의 요청이 을 처리하다가 I/O작업을 만나면 A의 요청으로 돌아감
과 같이, 동시에 2개이상의 requests를 다룰 수 있을것을 기대 합니다.
하지만 실제로는
- A의 요청이 들어오고, 이 요청을 처리하다가 I/O작업을 마주침
- I/O작업동안 A의 다른 작업을 진행함
을 수행한다는 것입니다.
2. Flask의 비동기 함수
flask app은 하나의 worker에 하나의 flask app을 호스팅 해줍니다.
이때 flask가 비동기 함수를 만나면 해당 작업을 처리하기 위해서app 내에서 thread를 생성하고
여기에서 aysncio이벤트루프를 실행시키는 구조로 되어있습니다.
그래서 동기함수를 연속으로 실행할 경우 ThreadID가 같지만
비동기함수를 연속으로 실행할 경우 ThreadID가 다른것을 확인할 수 있습니다.
3. 그럼 Flask 비동기함수는 어떨때 사용하나요?
Flask의 비동기 함수의 경우, 비동기함수를 사용할때 async/await
를 사용할 수 있다는 점이 있습니다.
하나의 요청을 처리할 때 I/O Bound가 동반되는 작업을 처리할 때 해당 작업을 비동기로 처리할 수 있다는 점이 장점이라고 할 수 있습니다.
import asyncio
from flask import Flask
import time
app = Flask(__name__)
async def myfetch(url, sess):
async with sess.get(url) as resp:
return url, resp.status, await resp.text()
@app.route("/data")
async def get_multiple_fetch():
import aiohttp
st = time.perf_counter()
target_url = ['www.naver.com', 'www.google.com', 'www.kakao.com', 'devocean.sk.com']*5
async with aiohttp.ClientSession() as sess:
ret = await asyncio.gather(*[myfetch(r"https://"+url, sess) for url in target_url])
print(f"async time : {time.perf_counter() - st}")
return "done"
@app.route("/data_sync")
def get_multiple_fetch_sync():
import requests
st = time.perf_counter()
target_url = ['www.naver.com', 'www.google.com', 'www.kakao.com', 'devocean.sk.com']*5
target_url = [r"https://"+i for i in target_url]
ret = []
with requests.Session() as sess:
for url in target_url:
resp = sess.get(r"https://"+url, verify=False)
ret.append((url, resp.status_code, resp.text))
print(f"sync time : {time.perf_counter() - st}")
return "done"
app.run(
host="127.0.0.1",
port = 8888,
processes = 1,
threaded = False
)
이처럼 하나의 requests를 처리할 대 다수의 I/O작업이 동반될 경우 활용을 고려해볼 수 있겠습니다.
4. 정말 이게 최선인가요?
Flask공식 홈페이지에서는 Flask Async관련 문서에서 대안을 제시해줍니다.
Quart = Flask.replace("WSGI", "ASGI")
(넝담😅)
공식 홈페이지에서는 Flask대신 Quart를 사용하라고 안내해주고 있습니다.
Quart같은 경우 Flask와 동일한 인터페이스를 제공하지만, 대신 내부 구현이 ASGI로 되어있는 웹프래임워크 입니다.
기존에 Flask를 사용했고, 비동기 함수는 사용하고 싶으면서 FastAPI는 새롭게 배워야 하는 경우 유용한 선택지 입니다.
Flask를 Quart를 바꿔주는 Monkey Patch를 통해서, ASGI기반의 Flask를 사용할 수 있게 됩니다.
import asyncio
#from flask import Flask
from quart import Quart
import time
#app = Flask(__name__)
app = Quart(__name__)
...
이제 2개의 Requests를 보낼경우
비동기 router의 경우, 두개의 요청을 동시에 처리하는것을 확인 할 수 있습니다.
'Python 잡지식' 카테고리의 다른 글
[Python]Python의 asyncio 탐구 (0) | 2024.07.10 |
---|---|
[Python]비동기로 parquet 파일 읽어들이기 (0) | 2024.07.06 |
[Python]Uvicorn에 대해서 (0) | 2024.05.08 |
[Python]Python 함수 실행간 메모리 측정하기 (0) | 2023.10.23 |
Numpy는 왜 빠를까?(SIMD, 데이터정렬) (0) | 2022.02.08 |
- Total
- Today
- Yesterday
- 코딩테스트
- stack
- GDC
- Greedy알고리즘
- hash
- AVX
- 완전탐색 알고리즘
- C++
- 동적계획법
- 자료구조
- Python
- 사칙연산
- heap
- prime number
- 분할정복
- git
- 이분탐색
- Sort알고리즘
- 프로그래머스
- 병렬처리
- 컴퓨터그래픽스
- SIMD
- Search알고리즘
- 알고리즘
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |