동시 프로그래밍은 여러 개의 쓰레드를 활용하여 이루어졌었다. 하지만 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에서도 비동기 프로그래밍을 더욱더 쉽게 할 수 있게 되었다.
Python 3.5부터 지원하는 asyncio
는 비동기 프로그래밍을 위한 모듈. 파이썬에서는 GIL때문에 비동기 프로그래밍이 동기 프로그래밍보다 느릴 수도 있다.
asyncio
는 이벤트 루프와 코루틴을 기반으로 동작하며 데이터를 요청하고 응답을 기다리는 I/O bound한 작업에서 효율적. 코루틴 기반이므로 멀티 스레드(CPU bound, 연산 중심)방식과 비교하여 문맥교환에 따른 비용이 다소 적게 들어간다.
일반적으로 연산이 많이 필요한 로직은 CPU bound
ex)데이터 마이닝, 이미지 프로세싱, 암호화폐 마이닝
로컬 파일 시스템 혹은 네트워크 통신이 많은 로직은 I/O bound라고 한다.
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)
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()
asyncio.wait()
async def start_coroutine():
await asyncio.wait([
coroutine_1(),
coroutine_2()
)]
asyncio.run(start_coroutine())
asyncio.sleep()
loop.run_in_executor(executor, func, *args)
- import
asyncio
async def
로 함수 선언await
로 비동기 함수의 요청 결과를 기다림asyncio.gather()
함수로 여러 비동기 함수를 동시에 호출asyncio.run()
함수로 비동기 함수 실행
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 초
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 초
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초
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