3.1 모듈 asyncio
비동기 작업을 위한 스레드를 이제는 아무도 만들지 않아. 물론 만들 수는 있지만, 그런 작업은 너무 저수준이라고 여겨지고 프레임워크 개발자들만 사용할 정도야. 그마저도 완전히 필요할 때만 그렇게 해.
지금은 비동기 프로그래밍, 동작자 async/await 그리고 코루틴과 태스크가 대세야. 하지만 차근차근 살펴보자~
역사적으로
처음에 Python에서는 비동기 프로그래밍 문제를 해결하기 위해 코루틴과 생성자를 기반으로 사용했어. 그러다가 Python 3.4에서는 asyncio 모듈이 나왔어 (가끔 async IO 라고도 불리는데) 여기에는 비동기 프로그래밍을 위한 메커니즘이 구현되어있어. Python 3.5에는 async/await 구조가 추가됐지.
이제 약간의 배경 정보를 알려줄게. 먼저 이들에 대해 간단히 설명하고, 자세히 설명하고, 그 후에 더 자세히 설명할게. 왜냐하면 거의 모두가 함께 작동하기 때문에 다른 것들을 참조하지 않고 한 가지를 자세히 설명하기는 어려워.
모듈 asyncio
모듈 asyncio는 비동기 프로그램을 작성하기 위한 것이고, 태스크들을 병렬로 실행할 수 있는 기능을 지원해. 비동기 입출력 작업, 타이머, 소켓, 코루틴 실행 및 멀티스레딩을 지원하고, 하나 또는 여러 개의 이벤트 루프에서 작동해.
코루틴 (Coroutines)
코루틴은 비동기 함수로, async def 키워드를 사용해서 정의돼. 코루틴은 await 키워드를 사용해서 자신의 실행을 일시 중단할 수 있어, 그 동안에 다른 코루틴이 실행될 수 있지.
import asyncio
# 비동기 함수 (코루틴) 정의
async def main():
print('Hello ...')
# 1초 동안 실행 중지
await asyncio.sleep(1)
print('... World!')
# 이벤트 루프에서 비동기 함수 main() 실행
asyncio.run(main())
이벤트 루프 (Event Loop)
이벤트 루프는 코루틴, 태스크 및 기타 비동기 작업의 실행을 관리해. asyncio.run() 호출은 이벤트 루프를 시작하고 코루틴이 완료될 때까지 실행해.
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
# 현재 이벤트 루프 가져오기
loop = asyncio.get_event_loop()
# 코루틴이 완료될 때까지 실행
loop.run_until_complete(main())
# 모든 태스크 완료 후 이벤트 루프 닫기
loop.close()
태스크 (Tasks)
태스크는 코루틴을 병렬로 실행할 수 있게 해. asyncio.create_task()나 asyncio.ensure_future()로 생성돼.
import asyncio
# 지연 실행되는 코루틴 정의
async def say_after(delay, what):
# 지정된 시간 동안 실행 중지
await asyncio.sleep(delay)
print(what)
# 메인 코루틴
async def main():
# 코루틴의 병렬 실행을 위한 태스크 생성
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
# 두 태스크가 완료될 때까지 대기
await task1
await task2
# 메인 코루틴 실행
asyncio.run(main())
퓨처 (Futures)
Future 객체는 비동기 작업의 결과를 나타내며, 미래에 사용할 수 있어. 예를 들어 비동기 태스크가 완료될 때까지 기다리는데 사용돼.
import asyncio
# 긴 작업을 모방한 코루틴 정의
async def long_running_task():
print('Task started')
# 3초 동안 실행 중지
await asyncio.sleep(3)
print('Task finished')
return 'Result'
# 메인 코루틴
async def main():
# 태스크 완료를 기다리기 위한 퓨처 생성
future = asyncio.ensure_future(long_running_task())
# 태스크 완료를 기다리고 결과 획득
result = await future
print(f'Task result: {result}')
# 메인 코루틴 실행
asyncio.run(main())
3.2 비동기 함수 — async def
비동기 함수는 일반 함수와 동일하게 선언되지만, def 키워드 앞에 async를 붙여야 해.
async def 함수이름(매개변수):
함수 코드
비동기 함수는 일반 함수처럼 선언되고 호출되지만 결과 값을 반환하는 게 아니라 특별한 객체, 코루틴을 반환해.
이것을 실제로 확인할 수도 있어:
import asyncio
async def main():
print("Hello World")
# 코루틴을 반환하는 비동기 함수 호출
result = main()
# 결과 타입 확인
print(type(result)) # <class 'coroutine'>
무슨 일이 일어나고 있는걸까? 당신이 함수를 async로 표시할 때, 사실상 이 데코레이터를 추가하는 거랑 비슷해:
def async_decorator(func):
# Task 객체 생성
task = Task()
# 우리 함수 func를 전달하여 이를 실행하게 함
task.target = func
# 해당 task 객체를 이벤트 루프의 태스크 큐에 추가
eventloop.add_task(task)
# task 객체 반환
return task
그리고 당신의 코드는 이처럼 변경돼:
import asyncio
@async_decorator
def main():
print("Hello World")
result = main()
print(type(result)) # <class 'coroutine'>
이 비유의 의미는 다음과 같아:
비동기 함수를 호출하면 특별한 객체 Task가 생성돼, 이 객체는 당신의 함수를 실행할 예정이지만, 미래의 어느 시점에 실행될거야. 0.0001 초 후일 수도 있고, 10초 후일 수도 있어.
이 task 객체가 당신의 비동기 함수 호출의 결과로 즉시 반환돼.이것이 바로 코루틴이야. 당신의 비동기 함수는 아직 실행되지 않았을 수도 있지만, task 객체(코루틴)는 이미 존재해.
왜 이 task 객체(코루틴)가 필요할까? 사실 할 수 있는 일은 많지 않지만, 3가지 일은 가능해:
- 비동기 함수가 실행될 때까지 기다리기.
- 비동기 함수가 끝날 때까지 기다리고, 코루틴에서 함수의 결과를 얻기.
- 10개(어떤 숫자든)의 비동기 함수가 실행될 때까지 기다리기.
어떻게 하는지는 아래에서 설명할게.
3.3 연산자 await
코루틴과 관련된 대부분의 작업은 "비동기 함수의 실행을 기다리는 것"에서 시작해. 그래서 이 작업을 위한 특별한 연산자 await가 있어.
단순히 코루틴 앞에 이를 쓰면 돼:
await 코루틴
또는 바로 비동기 함수 호출 앞에 작성하면 돼:
await 비동기_함수(인수)
Python이 연산자 await를 만났을 때, 현재 함수의 실행을 일시 중지하고 코루틴이 완료될 때까지 기다려 — 코루틴이 참조하는 비동기 함수가 완료될 때까지.
중요! 연산자 await는 오직 비동기 함수 내에서만 다른 코루틴 또는 비동기 작업이 완료될 때까지 실행을 중지하는 데 사용돼.
이는 비동기 함수 호출 사이의 전환 과정을 간소화하기 위해서야. await 호출은 실제로 "여기서 어떤 시간을 기다릴거니까 — 다른 비동기 함수들을 실행하세요"라는 선언이야.
예시:
import asyncio
# 비동기 함수 정의
async def async_print(text):
print(text)
# 메인 비동기 함수
async def main():
# 비동기 함수 실행을 기다리기 위해 await 사용
await async_print("Hello World")
# 메인 이벤트 루프 실행 및 코루틴 main() 수행
asyncio.run(main()) # 비동기 함수 실행
사실 await 연산자는 더 영리하게 작동해 — 비동기 함수의 실행 결과도 반환해 그 함수에서 호출됐던.
예시:
import asyncio
# 두 숫자를 더하는 비동기 함수 정의
async def async_add(a, b):
return a + b
# 메인 비동기 함수
async def main():
# async_add의 실행 결과를 얻기 위해 await 사용
sum = await async_add(100, 200)
print(sum)
# 메인 이벤트 루프 실행 및 코루틴 main() 수행
asyncio.run(main()) # 비동기 함수 실행
결론적으로, 연산자 await는:
- 다른 코루틴이나 비동기 작업이 완료될 때까지 현재 비동기 함수를 중단해.
- 비동기 작업 또는 코루틴의 실행 결과를 반환해.
- 오직 비동기 함수 내에서만 사용할 수 있어.
GO TO FULL VERSION