
비동기 처리 원리
비동기 처리의 핵심은 동시성을 제공하면서도 단일 스레드에서 실행된다는 점입니다. 이는 주로 I/O 작업(예: 파일 읽기/쓰기, 네트워크 요청 등)에서 유용합니다. 비동기 처리는 이러한 작업을 기다리는 동안 CPU가 다른 작업을 처리할 수 있도록 해줍니다.
주요 개념
-
이벤트 루프 (Event Loop):
- 비동기 처리의 중심입니다. 이벤트 루프는 여러 작업을 관리하고, 각 작업이 완료될 때까지 기다렸다가 다시 실행을 재개합니다.
- 이벤트 루프는 비동기 함수(코루틴)를 스케줄링하고, I/O 작업이 완료되면 해당 작업을 다시 실행합니다.
-
코루틴 (Coroutines):
async def로 정의된 함수입니다. 코루틴은 실행 중에await키워드를 만나면 실행을 일시 중단하고, 다른 작업이 실행될 수 있도록 제어를 이벤트 루프에 넘깁니다.- 코루틴은 비동기 함수로, 동기 함수와 달리 실행을 중단하고 나중에 다시 재개할 수 있습니다.
-
Futures:
- 아직 완료되지 않은 작업의 결과를 나타내는 객체입니다.
Future객체는 나중에 완료될 작업의 결과를 포함하며, 이벤트 루프가 해당 작업이 완료되면 결과를 반환합니다.
- 아직 완료되지 않은 작업의 결과를 나타내는 객체입니다.
-
Tasks:
- 코루틴을 실행하기 위해 이벤트 루프에 의해 관리되는 객체입니다.
asyncio.Task는 코루틴을 감싸서 실행을 관리하며, 완료되면Future객체로 결과를 반환합니다.
- 코루틴을 실행하기 위해 이벤트 루프에 의해 관리되는 객체입니다.
비동기 처리의 동작 원리
-
이벤트 루프 시작:
asyncio.run()또는asyncio.get_event_loop()를 호출하여 이벤트 루프를 시작합니다.- 이벤트 루프는 대기 중인 작업(코루틴)을 관리하고, 각 작업이 완료될 때까지 기다립니다.
-
코루틴 실행:
- 코루틴은
await키워드를 만나면 실행을 중단하고, 이벤트 루프는 다른 작업을 실행합니다. - 예를 들어, 네트워크 요청을 보내고 응답을 기다리는 동안 다른 코루틴이 실행될 수 있습니다.
- 코루틴은
-
I/O 작업 대기:
- 코루틴이
await를 통해 I/O 작업을 기다리는 동안, 이벤트 루프는 다른 코루틴을 실행합니다. 이로 인해 CPU가 유휴 상태로 남지 않고 다른 작업을 처리할 수 있습니다.
- 코루틴이
-
작업 완료:
- I/O 작업이 완료되면 이벤트 루프는 해당 코루틴을 다시 실행하여 남은 작업을 처리합니다.
예시 코드
import asyncio
async def fetch_data():
print("데이터 요청 중...")
await asyncio.sleep(2) # 네트워크 요청을 시뮬레이션
print("데이터 수신 완료")
return "데이터"
async def main():
print("비동기 작업 시작")
result = await fetch_data()
print(f"결과: {result}")
# 이벤트 루프 실행
asyncio.run(main())
코드 설명
-
fetch_data코루틴:async def로 정의된 비동기 함수입니다.await asyncio.sleep(2)는 2초 동안 대기하는 동안 다른 작업이 실행될 수 있도록 합니다.
-
main코루틴:fetch_data코루틴을 호출하고, 그 결과를 기다립니다.await키워드를 사용하여fetch_data가 완료될 때까지 기다립니다.
-
이벤트 루프 실행:
asyncio.run(main())을 통해 이벤트 루프를 시작하고,main코루틴을 실행합니다.
비동기 처리의 장점
- 효율적인 자원 사용: 비동기 처리는 I/O 작업을 기다리는 동안 CPU가 다른 작업을 처리할 수 있도록 하여 자원을 효율적으로 사용합니다.
- 동시성: 여러 작업을 동시에 처리할 수 있어, 특히 네트워크 요청이나 파일 입출력과 같은 I/O 바운드 작업에서 성능이 크게 향상됩니다.
결론
비동기 처리는 이벤트 루프와 코루틴을 사용하여 I/O 작업을 효율적으로 처리하는 방식입니다. 이를 통해 단일 스레드에서 여러 작업을 동시에 처리할 수 있으며, 특히 I/O 바운드 작업에서 성능을 크게 향상시킬 수 있습니다.
비동기 처리의 핵심은 동시성을 제공하는 것이지만, 이는 병렬 처리와는 다르다는 점을 기억해야 해. 이제 질문에 대한 답을 단계별로 설명할게.
비동기 작업의 흐름
-
작업 시작과 일시 중단:
- 비동기 함수(코루틴)는
await키워드를 만나면 실행을 일시 중단해. 이때, 해당 작업은 완전히 멈추는 것이 아니라, 다른 작업이 실행될 수 있도록 제어권을 이벤트 루프에 넘기는 것이야. - 예를 들어, 네트워크 요청을 보내고 응답을 기다리는 동안, CPU는 다른 작업을 처리할 수 있어. 이때, 첫 번째 작업은 네트워크 응답을 기다리며 멈춰있지만, 이벤트 루프는 다른 작업을 실행할 수 있는 상태가 돼.
- 비동기 함수(코루틴)는
-
다음 작업 시작:
- 첫 번째 작업이
await로 인해 일시 중단된 동안, 이벤트 루프는 다른 비동기 작업을 실행해. 이 작업도 마찬가지로await를 만나면 일시 중단될 수 있어. - 중요한 점은 첫 번째 작업이 멈춰있는 동안에도 이벤트 루프는 다른 작업을 계속해서 실행한다는 거야. 즉, 첫 번째 작업이 멈춘 상태에서 두 번째 작업이 시작되고, 두 번째 작업이
await를 만나면 다시 첫 번째 작업으로 돌아갈 수 있어.
- 첫 번째 작업이
-
작업이 동시에 유지되는가?:
- 첫 번째 작업이 멈춘 상태에서 두 번째 작업이 시작되지만, 첫 번째 작업은 여전히 이벤트 루프에 의해 관리되고 있어. 즉, 첫 번째 작업이 완전히 종료된 것이 아니라, 일시 중단된 상태로 유지되고 있는 거야.
- 두 번째 작업이 실행되는 동안, 첫 번째 작업은 백그라운드에서 대기하고 있다가, 다시 실행될 기회가 오면 재개돼.
비유를 통한 설명
비동기 처리를 이해하기 쉽게 비유해볼게:
- 첫 번째 작업은 네트워크 요청을 보내고 응답을 기다리는 중이라고 생각해보자. 이때, 네트워크 응답이 올 때까지 기다리는 동안 CPU는 할 일이 없어.
- 두 번째 작업은 파일을 읽는 작업이라고 하자. 첫 번째 작업이 응답을 기다리는 동안, CPU는 두 번째 작업을 처리할 수 있어.
- 첫 번째 작업이 응답을 기다리는 동안, 두 번째 작업이 실행되고, 두 번째 작업이 끝나면 다시 첫 번째 작업으로 돌아가서 응답을 처리해.
이 과정에서 두 작업이 동시에 실행되는 것처럼 보이지만, 실제로는 이벤트 루프가 작업을 번갈아가며 처리하는 거야. 즉, 첫 번째 작업이 멈춘 동안에도 두 번째 작업이 실행되고, 첫 번째 작업은 백그라운드에서 유지되다가 다시 실행될 기회를 얻는 거지.
코드 예시
import asyncio
async def task1():
print("Task 1 시작")
await asyncio.sleep(2) # 2초 동안 대기 (네트워크 요청 대기 시뮬레이션)
print("Task 1 완료")
async def task2():
print("Task 2 시작")
await asyncio.sleep(1) # 1초 동안 대기 (파일 읽기 시뮬레이션)
print("Task 2 완료")
async def main():
# 두 작업을 동시에 실행
await asyncio.gather(task1(), task2())
# 이벤트 루프 실행
asyncio.run(main())
코드 설명
-
task1:- 2초 동안 대기하는 작업이야. 이 작업은
await asyncio.sleep(2)로 인해 2초 동안 멈추고, 그동안 다른 작업이 실행될 수 있어.
- 2초 동안 대기하는 작업이야. 이 작업은
-
task2:- 1초 동안 대기하는 작업이야.
await asyncio.sleep(1)로 인해 1초 동안 멈추고, 그동안 다른 작업이 실행될 수 있어.
- 1초 동안 대기하는 작업이야.
-
asyncio.gather:- 두 작업을 동시에 실행해.
task1이 2초 동안 대기하는 동안,task2는 1초 동안 대기하고,task2가 끝나면 다시task1이 재개돼.
- 두 작업을 동시에 실행해.
결론
- 첫 번째 작업이 멈춘 동안에도 두 번째 작업은 실행될 수 있어. 하지만 첫 번째 작업은 완전히 종료된 것이 아니라, 일시 중단된 상태로 유지되고 있어.
- 비동기 처리는 여러 작업을 동시에 실행하는 것처럼 보이지만, 실제로는 이벤트 루프가 작업을 번갈아가며 처리하는 방식이야.