1. 글을 쓰게 된 이유
서비스 프로그램을 만들면서 asynico를 사용해서 코딩을 해야하는데
생각보다 비동기를 사용하면 굉장히 좋은 서비스를 만들 수 있고
속도 문제도 해결할 수 있다는 사실을 알게되었다.
2. 파이썬에서 비동기 프로그래밍
- 자바스크립트와 같이 애초에 비동기 방식으로 동작하도록 설계된 언어는 익숙한 개념이다
- 파이썬과 같이 동기 방식으로 동작하는 언어에서는 생소하지만
- 파이썬 3.4에서 asyncio 모듈이 표준 라이브러리로 추가되면서
- 파이썬 3.5에서 async / await 키워드가 문법으로 채택이 되면서
- 파이썬도 외부 라이브러리 없이 프로그래밍이 가능해졌다.
3. 핵심 문법
- def(동기 방식) / async def(비동기 방식)
아래와 같은 비동기 방식으로 만들어진 함수를 코루틴(coroutine)이라고 부름
async def test():
return "test"
- async def(비동기 함수)는 일반적으로 async 로 선언된 다른 비동기 함수 내에서 await 키워드를 붙여서 호출해야함.
async def main_test():
await test()
- async 로 선언되지 않은 일반 동기 함수 내에서는 비동기 함수를 호출하려면 asyncio 라이브러리의 이벤트 루프를 이용해야함.
loop = asyncio.get_event_loop()
loop.run_until_complete(main_test())
loop.close()
※ 파이썬 3.7 이상에서는 다음과 같이 한줄로 간단히 비동기 함수를 호출 가능하다.
asyncio.run(main_async())
- 비동기 프로그래밍 asyncio.sleep()은 CPU가 놀지 않고 다른 처리를 할 수 있도록 해준다. 여기도 await 키워드는 붙여야 한다.
(time.sleep()은 함수를 기다리는 동안 CPU를 그냥 놔둠)
import asyncio
async def number_test(n):
for i in range(1, n + 1):
print(f'{n}명 중 {i}번 째 사용자 조회 중')
await asyncio.sleep(1)
print(f'> 총 {n}명 사용자 비동기 조회 완료')
- asyncio.gather() 함수는 비동기 함수들을 병렬로 실행하고 그 결과를 모아주는 역할, 인자로 넘어오는 비동기 함수들을 동시에 실행하고 각 비동기 함수가 변환하는 결과들을 모아서 리스트로 반환
단, 비동기 처리는 정확히 실행 순서가 보장되지 않기 때문에 코드를 작성할 때 꼭 고려해야 한다.
import asyncio
import time
async def number_test(n):
for i in range(1, n + 1):
print(f'{n}명 중 {i}번 째 사용자 조회 중')
await asyncio.sleep(1)
print(f'> 총 {n}명 사용자 비동기 조회 완료')
async def process_async():
start = time.time()
await asyncio.gather(
number_test(3),
number_test(2),
number_test(1),
)
end = time.time()
print(f'>>> 비동기 처리 총 소요 시간: {end - start}')
if __name__ == '__main__':
asyncio.run(process_async())
- 코루틴과 테스크의 차이
1. 코루틴을 사용했을 때
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
2. 테스크를 사용했을 때
import asyncio
import time
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'))
print(f"started at {time.strftime('%X')}")
# Wait until both tasks are completed (should take
# around 2 seconds.)
await task1
await task2
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
Q. 코루틴을 사용했을 때는 3초, 테스크를 사용했을 때는 2초가 걸리는 이유는 무엇일까?
A. async def로 선언 된 함수를 호출하면, 코드가 실행 되지 않고 코루틴 객체를 리턴하기만 할 뿐이다.
하지만, create_task는 반환된 객체를 가지고 비동기 작업 객체인 테스크를 만들고 실행한다.
간단하게 말해서, 코루틴은 비동기 작업의 단위 / create_task()는 비동기 작업을 관리하고 실행하기 위한 도구다.
코루틴에서는 async def안에 작성한 코드가 실행되는 시점은
await say_after(1, "hello")
입니다. say_after(1, "hello") 호출로 코루틴 객체가 생성되고, await에 의해 함수 내 작성한 코드가 동기적으로 실행되면서 끝날 때 까지 대기합니다. 반면 테스크는,
task1 = asyncio.create_task(
say_after(1, 'hello'))
이 부분에서 이미 코드를 비동기로 실행했습니다.
그리고 await task 는 이미 실행한 코드를 대기할 뿐입니다. 따라서 task1과 task2의 await 순서를 변경해도 같은 결과를 얻습니다.
4. 출처
'Python > Data Engineering' 카테고리의 다른 글
Kafka 설치하는 방법 (feat. ubuntu) (0) | 2024.04.03 |
---|---|
MLOps 기술들 설명 (데이터, 모델, 서빙 등) (1) | 2023.12.29 |
Ray를 활용한 Python 병렬 처리 하기 (feat. gpt api) (0) | 2023.08.22 |
DataFrame에서 줄 바꿈, 띄어쓰기 중복 제거 (0) | 2023.02.10 |
한국어 문장 분리기 (kss - korean sentence splitter) 사용방법 (0) | 2023.02.10 |