동기 vs 비동기
일반적인 동기식 프로그래밍에서는 작업이 순차적으로 실행되며, 한 작업이 완료될 때까지 다음 작업을 기다림
반면에 비동기식 프로그래밍에서는 작업을 병렬로 실행하고, 작업이 완료될 때까지 기다리지 않고 다른 작업을 계속할 수 있음
비동기 프로그래밍은 주로 입출력이나 네트워크 작업과 같이 시간이 오래 걸리는 작업을 수행할 때 유용함
이를 통해 여러 작업을 동시에 처리할 수 있고, 시스템 자원을 효율적으로 활용할 수 있음
asyncio
asyncio는 파이썬의 비동기 프로그래밍을 지원하는 라이브러리로, 이벤트 기반의 비동기 프로그램을 작성할 때 유용함
대규모의 동시성 작업을 처리하고, 네트워크와 파일 입출력을 효율적으로 다룰 수 있게 해 줌
asyncio의 핵심은 코루틴과 이벤트루프라는 개념
이벤트 루프는 비동기 작업을 관리하고 실행하는 역할이고, 코루틴은 비동기 함수로 중단된 지점에서 실행을 일시 중단하고 다른 작업을 수행한 뒤 재개할 수 있게 함
asyncio에서의 코루틴
코루틴은 async def 키워드를 사용하여 정의됨
asyncio에서는 await 키워드를 사용하여 코루틴을 호출하고 실행
import asyncio
async def example_coroutine():
print("Coroutine started")
await asyncio.sleep(1)
print("Coroutine finished")
async def main():
await example_coroutine()
asyncio.run(main())
asyncio에서의 이벤트 루프
asyncio에서 이벤트 루프는 비동기 작업을 관리하고 실행하는 핵심 요소
이벤트 루프는 이벤트를 감지하고 처리하는 무한 루프로, 비동기 작업을 실행하고 완료될 때까지 대기하는 역할을 함
이를 통해 여러 비동기 작업을 동시에 실행하고, 작업이 완료될 때까지 기다리지 않고 다른 작업을 수행할 수 있음
시간이 오래 걸리는 입출력 작업을 비동기적으로 처리하여 프로그램의 성능을 향상하는 데 유용함
asyncio에서는 asyncio.get_event_loop 함수를 사용하여 이벤트 루프를 얻고, asyncio.run 메서드를 사용하여 비동기 작업을 실행함
import asyncio
import aiofiles
async def write_to_file(file_name, content):
print(f"Writing to file: {file_name}")
async with aiofiles.open(file_name, 'w') as file:
await file.write(content)
print(f"Content successfully written to {file_name}")
async def main():
file_contents = [
("file1.txt", "Hello, World!"),
("file2.txt", "Async I/O is awesome!"),
("file3.txt", "Python is powerful!")
]
# 비동기 파일 쓰기 작업 등록
tasks = [write_to_file(file_name, content) for file_name, content in file_contents]
# 비동기 작업 실행
await asyncio.gather(*tasks)
# 이벤트 루프를 생성하여 main 함수 실행
asyncio.run(main())
더보기
실행결과
Writing to file: file1.txt
Writing to file: file2.txt
Writing to file: file3.txt
Content successfully written to file1.txt
Content successfully written to file2.txt
Content successfully written to file3.txt
asyncio에서의 동시성 제어
동시성 제어는 여러 작업이 동시에 실행될 때 발생하는 문제를 관리하고 해결하는 것
경쟁 조건과 교착 상태 같은 문제를 방지하여 프로그램의 안정성을 보장함
asyncio에서는 Lock, Semaphore, Queue 등의 동시성 제어 도구를 이용하여 여러 작업 간의 상호작용을 조절하고, 동기화를 유지할 수 있음
Lock를 이용한 동시성 제어 예제 코드
import asyncio
async def worker(lock, task_name):
print(f"{task_name} is trying to acquire the lock")
async with lock:
print(f"{task_name} has acquired the lock")
await asyncio.sleep(1)
print(f"{task_name} is releasing the lock")
async def main():
# 생성된 Lock 객체
lock = asyncio.Lock()
# 두 개의 작업이 동시에 실행되지만, 하나의 작업만이 lock을 확보하도록 하여 동시성 제어
await asyncio.gather(
worker(lock, "Task 1"),
worker(lock, "Task 2")
)
asyncio.run(main())
더보기
실행결과
Task 1 is trying to acquire the lock
Task 1 has acquired the lock
Task 2 is trying to acquire the lock
Task 1 is releasing the lock
Task 2 has acquired the lock
Task 2 is releasing the lock
Semaphore를 이용한 동시성 제어 예제 코드
import asyncio
async def worker(semaphore, task_name):
async with semaphore:
print(f"{task_name} is entering the semaphore")
await asyncio.sleep(1)
print(f"{task_name} is leaving the semaphore")
async def main():
# Semaphore 객체 생성
semaphore = asyncio.Semaphore(2) # 최대 2개의 작업을 동시에 실행
# Semaphore를 공유하는 세 개의 작업
await asyncio.gather(
worker(semaphore, "Task 1"),
worker(semaphore, "Task 2"),
worker(semaphore, "Task 3")
)
asyncio.run(main())
더보기
실행결과
Task 1 is entering the semaphore
Task 2 is entering the semaphore
Task 1 is leaving the semaphore
Task 2 is leaving the semaphore
Task 3 is entering the semaphore
Task 3 is leaving the semaphore
Queue를 이용한 동시성 제어 예제 코드
import asyncio
async def producer(queue):
for i in range(5):
print(f"Producing {i}")
await queue.put(i)
await asyncio.sleep(1)
async def consumer(queue, consumer_name):
while True:
item = await queue.get()
print(f"{consumer_name} consumed {item}")
queue.task_done()
async def main():
# 크기가 3인 Queue 생성
queue = asyncio.Queue(maxsize=3) # 최대 3개의 작업을 동시에 실행
# Producer와 Consumer 생성
producer_task = asyncio.create_task(producer(queue))
consumer_task1 = asyncio.create_task(consumer(queue, "Consumer 1"))
consumer_task2 = asyncio.create_task(consumer(queue, "Consumer 2"))
# Producer가 종료될 때까지 기다림
await producer_task
# Consumer가 모든 작업을 처리할 때까지 기다림
await queue.join()
# Consumer들에게 종료를 알림
consumer_task1.cancel()
consumer_task2.cancel()
asyncio.run(main())
더보기
실행결과
Producing 0
Consumer 1 consumed 0
Producing 1
Consumer 1 consumed 1
Producing 2
Consumer 2 consumed 2
Producing 3
Consumer 1 consumed 3
Producing 4
Consumer 2 consumed 4