asyncio
: `async/await` 구문을 사용하여 동시성 코드를 작성할 수 있게 해주는 라이브러리
- 단일 스레드 작업을 병렬로 처리
- 고성능 네트워크 및 웹 서버, 데이터베이스 연결 라이브러리, 분산 작업 큐 등을 제공하는 여러 파이썬 비동기 프레임워크의 기반으로 사용
- 종종 IO 병목이면서 고수준의 구조화된 네트워크 코드에 가장 적합
파이썬 3.7 버전 이상부터 사용 가능
고수준 API
- 파이썬 코루틴들을 동시에 실행하고 실행을 완전히 제어할 수 있음
- 네트워크 IO와 IPC를 수행
- 자식 프로세스를 제어
- 큐를 통해 작업을 분산
- 동시성 코드를 동기화
저수준 API
- 네트워킹 , 하위 프로세스 실행 , OS 신호 처리 등을 위한 비동기 API를 제공하는 이벤트 루프를 생성, 관리
- 트랜스포트를 사용하여 효율적인 프로토콜을 구현
- 콜백 기반 라이브러리와 async/await 구문을 사용한 코드 간에 다리를 놓음
예제
e.g. 서로 다른 입력 값으로 `sum()` 함수를 2번 수행하여 결괏값을 출력
import time
def sleep():
time.sleep(1)
def sum(name, numbers):
start = time.time()
total = 0
for number in numbers:
sleep()
total += number
print(f'작업중={name}, number={number}, total={total}')
end = time.time()
print(f'작업명={name}, 걸린시간={end-start}')
return total
def main():
start = time.time()
result1 = sum("A", [1, 2])
result2 = sum("B", [1, 2, 3])
end = time.time()
print(f'총합={result1+result2}, 총시간={end-start}')
if __name__ == "__main__":
main()
`sum()` 함수에서 입력 값을 하나씩 더할 때마다 `sleep()` 함수에 의해 1초씩 시간 소요
실행 결과
작업중=A, number=1, total=1
작업중=A, number=2, total=3
작업명=A, 걸린시간=2.000162124633789
작업중=B, number=1, total=1
작업중=B, number=2, total=3
작업중=B, number=3, total=6
작업명=B, 걸린시간=3.0002427101135254
총합=9, 총시간=5.0004048347473145
총 5초의 시간이 걸리고, 총합은 9
→ `sum()` 함수를 비동기 방식으로 호출하면 실행 시간을 줄일 수 있을 것
asyncio 모듈 적용
import asyncio
import time
async def sleep():
await asyncio.sleep(1)
async def sum(name, numbers):
start = time.time()
total = 0
for number in numbers:
await sleep()
total += number
print(f'작업중={name}, number={number}, total={total}')
end = time.time()
print(f'작업명={name}, 걸린시간={end-start}')
return total
async def main():
start = time.time()
task1 = asyncio.create_task(sum("A", [1, 2]))
task2 = asyncio.create_task(sum("B", [1, 2, 3]))
await task1
await task2
result1 = task1.result()
result2 = task2.result()
end = time.time()
print(f'총합={result1+result2}, 총시간={end-start}')
if __name__ == "__main__":
asyncio.run(main())
실행 결과
c:\projects\pylib>python asyncio_sample.py
작업중=A, number=1, total=1
작업중=B, number=1, total=1
작업중=A, number=2, total=3
작업명=A, 걸린시간=2.000617742538452
작업중=B, number=2, total=3
작업중=B, number=3, total=6
작업명=B, 걸린시간=3.000927209854126
총합=9, 총시간=3.000927209854126
A 작업과 B 작업을 교대로 호출 (제어권이 `await`에 의해 계속 바뀜)
시간도 5초 걸리던 것이 3초만 걸리게 되므로
A, B 작업이 완전히 비동기적으로 동작했다는 것을 알 수 있음
1) 코루틴
함수를 비동기로 호출하려면 `def` 앞에 `async` 키워드를 넣으면 됨
→ 비동기 함수가 됨
*코루틴: `async`를 적용한 비동기 함수
2) await
코루틴 안에서 다른 코루틴을 호출할 때, `await sleep()`과 같이 `await`를 함수명 앞에 붙여 호출해야 함
*넌블록킹(non-blocking): 코루틴 수행 중 `await 코루틴`을 만나면 `await`로 호출한 코루틴이 종료될 때까지 기다리지 않고, 제어권을 메인 스레드나 다른 코루틴으로 넘김
그리고 호출한 코루틴이 종료되면 이벤트에 의해 다시 그 이후 작업 수행
3) asyncio.sleep(1)
`sleep()` 함수에서 `time.sleep(1)` 대신 `asyncio.sleep(1)`를 사용
... 코루틴이 아닌 `time.sleep(1)`을 사용한다면 `await`가 적용되지 않아 실행 시간을 줄일 수 없음
4) asyncio.create_task()
`asyncio.create_task()`: 수행할 코루틴 작업(태스크) 생성
→ 작업을 생성할 뿐, 실제로 코루틴이 수행되는 것은 아님
실제 코루틴 실행은 `await 태스크`가 담당
실행 태스크의 결괏값은 `태스크.result()`로 얻을 수 있음
⭐ `asyncio.create_task()`는 코루틴을 동시에 실행하는 데 꼭 필요
다음처럼 태스크가 아닌 `await`로 코루틴을 실행한다면
코루틴이 동시에 실행되지 않고, 하나씩 차례로 실행되어 이득이 없을 것
result1 = await sum("A", [1, 2])
result2 = await sum("B", [1, 2, 3])
5) asyncio.run(main())
`asyncio.run(main())`: 런 루프를 생성하여 `main()` 코루틴을 실행
- 코루틴을 실행하려면 런 루프가 반드시 필요
- 코루틴이 모두 비동기적으로 실행되기 때문에 그 시작과 종료를 감지할 수 있는 이벤트 루프가 반드시 필요하기 때문
참고 문서
'Python > Study' 카테고리의 다른 글
[Python] Snap7 ─ Siemens S7 PLC 통신 라이브러리 (0) | 2024.11.22 |
---|---|
[Python] asyncpg 비동기 함수 정리 (2) | 2024.11.21 |
[Python] asyncpg ─ PostgreSQL 비동기 연결 (0) | 2024.11.21 |