[TIL] Python | 비동기 프로그래밍 (Async)
📌
동시 프로그래밍은 여러 개의 쓰레드를 활용하여 이루어졌었다. 하지만 thread safe
한 프로그램을 작성하는 것은 어렵다. 특히 싱글 코어 프로세서에서 이런 프로그램을 돌렸을때, 성능이 향상되지 않거나 떨어지는 경우도 존재한다.
따라서 하나의 쓰레드로 동시 처리를 하는 비동기 프로그래밍(asynchronous programming)에 대해서 알아보자.
-
동기 함수 (sync function)
함수 호출 -> 함수의 처음부터 진행 -> 끝 또는 return문을 만나면 종료.
리턴 했다는 것은 함수가 실행 완료 되었다는 것을 보장해줌. -
비동기 함수
함수를 호출 -> 실행이 완료 되지 않더라도 호출자에게 return, 제어권을 넘기고 자기 혼자 백그라운드로 작업을 계속 진행 -> 작업이 완료 되면 호출자에게 작업이 완료 되었음을 통보
리턴 되고 제어권이 호출자에게 넘어와도, 작업이 완료 되었음을 보장하지 않음.
비동기 프로그래밍 개념
웹 서버와 같은 애플리케이션의 경우 CPU 연산 시간 보다 DB나 API와 연동 과정에서 발생하는 대기 시간이 훨씬 길다.
비동기 프로그래밍은 이러한 대기 시간을 낭비하지 않고 그 시간에 CPU가 다른 처리를 할 수 있도록 함 -> non-blocking
: I/O 작업이 진행되는 동안 유저 프로세스의 작업을 중단시키지 않는 방식
동기란 빨래를 시작하고 종료가 되면 설거지를 시작하고 완료가 되면 TV를 보는 것처럼 한 번에 하나의 작업을 하는 것이고, 비동기는 빨래를 시작시키고 설거지를 하면서 TV를 보는 것과 같이 여러 작업을 동시에 하는 것과 같은 행동을 하는 것이다.
JavaScript는 비동기 방식으로 동작하도록 설계된 언어인 반면, Python은 동기 방식으로 동작하도록 설계된 언어 이기 때문에 생소한 개념일 수 있다.
하지만 Python 3.4 버전부터asyncio
라이브러리가 표준으로 채택되고 Python 3.5 버전부터async/await
키워드가 추가되면서, Python에서도 비동기 프로그래밍을 더욱더 쉽게 할 수 있게 되었다.
asyncio란?
Python 3.5부터 지원하는 asyncio
는 비동기 프로그래밍을 위한 모듈. 파이썬에서는 GIL때문에 비동기 프로그래밍이 동기 프로그래밍보다 느릴 수도 있다.
asyncio
는 이벤트 루프와 코루틴을 기반으로 동작하며 데이터를 요청하고 응답을 기다리는 I/O bound한 작업에서 효율적. 코루틴 기반이므로 멀티 스레드(CPU bound, 연산 중심)방식과 비교하여 문맥교환에 따른 비용이 다소 적게 들어간다.
일반적으로 연산이 많이 필요한 로직은 CPU bound
ex)데이터 마이닝, 이미지 프로세싱, 암호화폐 마이닝
로컬 파일 시스템 혹은 네트워크 통신이 많은 로직은 I/O bound라고 한다.
🟩 Event Loop
- 이벤트 루프는 작업들을 반복 하면서(루프를 돌면서) 하나씩 실행시킴
- 실행된 작업이 특정한 데이터를 요청 후 응답을 기다려야 하면, 이 작업은 다시 이벤트 루프에 통제권을 넘겨줌
- 통제권을 받은 이벤트루프는 다음 작업을 실행
- 이전 요청작업의 응답을 받으면 순서대로 멈췄던 부분부터 다시 통제권을 가지고 작업을 마무리함
🟨 Coroutine
- 코루틴은 파이썬에서 async를 통해 생성된 비동기 함수 객체를 뜻함
- 응답이 지연되는 부분에서 이벤트 루프에 통제권을 줄 수 있음
- 요청이 완료되면 멈춘부분부터 기존의 상태를 유지한 채 남은 작업을 완료 할 수 있는 함수
- 코루틴이 아닌 일반적인 함수는 Subroutine(서브루틴)이라고 함
🟪 Asyncio
-
async/await
문법을 사용해서 cpu작업과 I/O를 병렬적으로 처리하여 비동기 프로그래밍을 할 수 있도록 하는 파이썬 라이브러리 -
async
- 비동기 함수를 생성하는 키워드
def
앞에async
키워드를 붙여서 비동기 함수를 생성.async
키워드를 통해 생성된 비동기 함수를 코루틴 이라고 함
-
await
- 비동기 함수를 호출하는 키워드
- 코루틴을 일반 함수와 같이 호출하면 코루틴 객체를 반환함
await
키워드를 통해서 호출해야 비동기 함수를 실행 가능
# 사용 예시
async def async_func():
pass
async def run_async_func():
await async_func()
🟦 사용 함수 예시
-
asyncio.get_event_loop()
- 현재의 이벤트 루프를 반환하는 함수
async
로 선언되지 않은 일반 함수에서 비동기 함수를 호출하기 위해서는 이벤트 루프를 사용해야 함
-
loop.run_until_complete(future)
- future가 완료할 때 까지 이벤트 루프를 실행시킴
- 실행한 결과(future)의 결과를 반환
-
asyncio.Future
- 비동기 연산의 최종 결과를 나타내는 객체
async def async_func(): pass loop = asyncio.get_event_loop() # 이벤트 루프 정의 loop.run_until_complete(async_func()) # 비동기 함수 async_func를 호출 loop.close() # 이벤트 루프 종료
- 비동기 연산의 최종 결과를 나타내는 객체
-
asyncio.gather()
- 여러 함수를 동시에 호출할 때 사용
loop.run_until_complete(asyncio.gather(coroutine_1(), coroutine_2()))
- 여러 함수를 동시에 호출할 때 사용
-
asyncio.run()
- 간단하게 비동기 함수를 호출하는 함수
- Python 3.7 부터 사용 가능
-
asyncio.wait()
- 여러개의 함수를 리스트에 담아서 호출 가능
async def start_coroutine(): await asyncio.wait([ coroutine_1(), coroutine_2() )] asyncio.run(start_coroutine())
- 여러개의 함수를 리스트에 담아서 호출 가능
-
asyncio.sleep()
- 비동기 함수로써 시작하자 마자 바로 리턴하지만, 백그라운드로 1초간 대기 후 시간이 만료 되면 만료를 통보한다.
-
loop.run_in_executor(executor, func, *args)
- 동기 함수를 비동기로 동작 할 수 있도록 하는 함수
- 지정된 executor에서 func가 호출되도록 등록한다. executor 가 None 이면 기본 executor가 사용 된다.
- import
asyncio
async def
로 함수 선언await
로 비동기 함수의 요청 결과를 기다림asyncio.gather()
함수로 여러 비동기 함수를 동시에 호출asyncio.run()
함수로 비동기 함수 실행
asyncio를 사용한 비동기 프로그래밍 예제
1️⃣ sleep 함수를 이용한 소요 시간 비교
- 동기 방식 : task1 실행 후 task2 실행 -> 총 8초의 시간이 걸림
def sync_task_1():
print('sync_task_1 : 시작')
print('sync_task_1 : 5초 대기')
time.sleep(5)
print('sync_task_1 : 종료')
def sync_task_2():
print('sync_task_2 : 시작')
print('sync_task_2 : 3초 대기')
time.sleep(3)
print('sync_task_2 : 종료')
start_time = time.time()
sync_task_1()
sync_task_2()
end_time = time.time()
print (end_time-start_time)
# 8.009934186935425 초
- 비동기 방식 : task1을 실행하는 동안 task2를 실행함 -> 총 5초의 시간이 걸림
async def async_task_1():
print('async_task_1 : 시작')
print('async_task_1 : 5초 대기')
await asyncio.sleep(5)
print('async_task_1 : 종료')
async def async_task_2():
print('async_task_2 : 시작')
print('async_task_2 : 3초 대기')
await asyncio.sleep(3)
print('async_task_2 : 종료')
async def main():
start_time = time.time()
# 여러 코루틴을 동시에 실행하기 위해선 create_task()를 사용
# await async_task_1()
# await async_task_2() <- 이런식으로 나열하면 코루틴을 호출하는 것이지 다음 태스크를 실행하도록 예약하는 행동이 아님
task1 = asyncio.create_task(async_task_1())
task2 = asyncio.create_task(async_task_2())
await task1
await task2
end_time = time.time()
print(f"소요시간 : {end_time - start_time}")
asyncio.run(main())
# 소요 시간 : 5.003206968307495 초
2️⃣ 웹 페이지를 불러올 때의 소요 시간 비교
- 동기 방식
def download_page(url):
req = requests.get(url)
html = req.text
print('다운로드 완료 :', url, ',페이지 크기 (', len(html),')')
def main():
download_page('https://velog.io/@fore0919/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-K%EB%B2%88%EC%A7%B8-%EC%88%98-%EC%A0%95%EB%A0%AC-Python')
download_page('https://velog.io/@fore0919/TIL-WEB-TCPIP-%EA%B0%9C%EB%85%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0')
download_page('https://velog.io/@fore0919/TIL-Python-sleep-%ED%95%A8%EC%88%98')
download_page('https://velog.io/@fore0919/TIL-Middleware')
download_page('https://velog.io/@fore0919/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC-%EC%99%84%EC%A0%84-%ED%83%90%EC%83%89-Python')
download_page('https://velog.io/@fore0919/DB-Data-Type%EC%9E%90%EB%A3%8C%ED%98%95-MySQL')
download_page('https://velog.io/@fore0919/DB-SQL-Constaint-%EC%A0%9C%EC%95%BD-%EC%A1%B0%EA%B1%B4')
print (f"시작 시간{time.strftime('%X')}")
start_time = time.time()
main()
finish_time = time.time()
print(f"종료 시간{time.strftime('%X')}, 총 소요 시간 : {finish_time - start_time}초")
# 총 소요 시간 : 3.3226277828216553초
- 비동기 방식
async def download_page2(url):
loop = asyncio.get_event_loop() # 이벤트 루프 객체 얻기
req = await loop.run_in_executor(None, requests.get, url) # 동기함수인 requests.get을 비동기로 호출
html = req.text
print('다운로드 완료 :', url, ',페이지 크기 (', len(html),')')
async def main():
await asyncio.gather(
download_page2('https://velog.io/@fore0919/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-K%EB%B2%88%EC%A7%B8-%EC%88%98-%EC%A0%95%EB%A0%AC-Python'),
download_page2('https://velog.io/@fore0919/TIL-WEB-TCPIP-%EA%B0%9C%EB%85%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0'),
download_page2('https://velog.io/@fore0919/TIL-Python-sleep-%ED%95%A8%EC%88%98'),
download_page2('https://velog.io/@fore0919/TIL-Middleware'),
download_page2('https://velog.io/@fore0919/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC-%EC%99%84%EC%A0%84-%ED%83%90%EC%83%89-Python'),
download_page2('https://velog.io/@fore0919/DB-Data-Type%EC%9E%90%EB%A3%8C%ED%98%95-MySQL'),
download_page2('https://velog.io/@fore0919/DB-SQL-Constaint-%EC%A0%9C%EC%95%BD-%EC%A1%B0%EA%B1%B4')
)
print (f"시작 시간{time.strftime('%X')}")
start_time = time.time()
asyncio.run(main())
finish_time = time.time()
print(f"종료 시간{time.strftime('%X')}, 총 소요 시간 : {finish_time - start_time}초")
# 총 소요 시간 : 0.7937102317810059초
참고 자료 (Reference)
https://docs.python.org/ko/3.8/library/asyncio.html
https://kukuta.tistory.com/345
https://jammdev.tistory.com/37
https://brownbears.tistory.com/540
https://dojang.io/mod/page/view.php?id=2469
Author And Source
이 문제에 관하여([TIL] Python | 비동기 프로그래밍 (Async)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@fore0919/TIL-Python-비동기-프로그래밍-Async저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)