Python의 Async/Await: 코루틴에 대한 완벽한 가이드
James Reed
Infrastructure Engineer · Leapcell

Python 비동기 프로그래밍
Python에서는 코루틴, 멀티스레딩, 멀티프로세싱과 같은 여러 가지 비동기적 접근 방식이 있습니다. 또한 일부 전통적인 방법과 타사 비동기 라이브러리도 있습니다. 이 기사에서는 주로 코루틴에 중점을 두고 멀티스레딩 및 멀티프로세싱을 간략하게 소개합니다.
async/await
Python에서 async
로 선언된 함수는 비동기 함수이며, 종종 코루틴이라고 합니다. 예를 들어:
import asyncio async def hello(): await asyncio.sleep(1) print("hello leapcell")
호출 방법
비동기 함수를 호출하는 방법은 일반 함수와 약간 다릅니다. 예를 들어, 일반 함수를 호출하는 방법은 다음과 같습니다.
def hello(): print("hello leapcell") hello()
그리고 비동기 함수를 호출하는 방법은 다음과 같습니다.
import asyncio async def hello(): await asyncio.sleep(1) print("hello leapcell") h = hello() asyncio.run(h)
비동기 함수를 호출할 때 먼저 h = hello()
를 사용하면 코루틴 객체가 반환되고 함수 내의 코드는 실행되지 않습니다. asyncio.run(h)
함수 또는 await h
구문을 사용해야 코드가 실행됩니다. 아래 예시를 참조하십시오:
import asyncio async def async_function(): print("This is inside the async function") await asyncio.sleep(1) return "Async function result" # 올바른 사용법 async def correct_usage(): print("Correct usage:") result = await async_function() print(f"Result: {result}") # await를 사용하지 않고 호출 def incorrect_usage(): print("\nIncorrect usage:") coroutine = async_function() print(f"Returned object: {coroutine}") # Note: "This is inside the async function" won't be printed here # 처리되지 않은 코루틴 처리 async def handle_unawaited_coroutine(): print("\nHandling unawaited coroutine:") coroutine = async_function() try: # asyncio.run()을 사용하여 코루틴 실행 result = await coroutine print(f"Result after handling: {result}") except RuntimeWarning as e: print(f"Caught warning: {e}") async def main(): await correct_usage() incorrect_usage() await handle_unawaited_coroutine() asyncio.run(main())
비동기 함수 호출에 대한 일반적인 방법
asyncio.gather()
gather
를 사용하면 여러 작업을 동시에 시작하고 동시에 실행합니다. 모든 작업이 완료되고 결과가 반환된 후 후속 코드가 계속 실행됩니다. 예를 들어:
import asyncio async def num01(): await asyncio.sleep(1) return 1 async def num02(): await asyncio.sleep(1) return 2 async def combine(): results = await asyncio.gather(num01(), num02()) print(results) asyncio.run(combine())
출력:
[1, 2]
위에는 두 개의 비동기 함수가 있습니다. 이제 asyncio.gather
를 사용하여 이 두 함수를 동시에 실행한 다음 await
를 사용하여 결과를 기다립니다. 반환된 결과는 results
에 저장됩니다.
await 직접 사용
위의 gather
메서드는 여러 비동기 함수를 수집하여 동시에 실행합니다. 이 방법 외에도 아래와 같이 await
키워드를 직접 사용할 수도 있습니다.
import asyncio async def hello(): await asyncio.sleep(1) return "hello leapcell" async def example(): result = await hello() print(result) asyncio.run(example())
출력:
hello leapcell
위의 example
함수에서 await
는 비동기 함수의 결과를 기다리는 데 사용됩니다. 결과가 반환된 후 콘솔에 출력됩니다. 이 방법은 실제로 순차적으로 실행됩니다. 코드는 결과가 반환될 때까지 await
문에서 기다린 후 다음 코드를 계속 실행하기 때문입니다.
여기서 기다리지 않으면 어떻게 될까요? result = hello()
를 사용하면 hello()
내부의 코드가 실행되지 않고 반환된 result
는 코루틴 객체입니다.
asyncio.create_task()
위의 방법 외에도 asyncio.create_task()
를 사용하는 보다 유연한 방법이 있습니다. 이 메서드는 작업을 생성하고 백그라운드에서 즉시 실행합니다. 이때 주 함수는 다른 작업을 수행할 수 있습니다. 비동기 작업의 결과를 얻어야 할 때 await
를 사용하여 가져옵니다. 아래와 같습니다.
import asyncio async def number(): await asyncio.sleep(1) return 1 async def float_num(): await asyncio.sleep(1) return 1.0 async def example(): n = asyncio.create_task(number()) f = asyncio.create_task(float_num()) print("do something...") print(await n) print(await f) asyncio.run(example())
출력:
do something...
1
1.0
위의 출력에서 create_task
는 먼저 작업을 생성하고 시작한다는 것을 알 수 있습니다. 이때 주 함수는 차단되지 않고 다음 코드를 계속 실행합니다. 비동기 함수의 결과가 필요한 경우 await n
을 호출하여 결과를 얻습니다. 이런 식으로 시간이 오래 걸리는 작업을 비동기 코드로 넣어 실행한 다음 필요할 때 이러한 비동기 함수의 결과를 얻을 수 있습니다.
참고: create_task
를 사용하여 비동기 함수를 호출하는 것은 일반 함수처럼 호출하는 것과 다릅니다. 일반 함수 호출 방법 number()
를 사용하면 함수가 실행되지 않습니다. 그러나 create_task
를 사용하여 비동기 함수를 호출하면 함수가 즉시 실행됩니다. await
를 사용하여 결과를 얻지 않더라도 주 함수가 종료되기 전에 함수가 실행을 완료합니다.
Semaphore
asyncio.Semaphore
는 Python의 asyncio
라이브러리의 동기화 프리미티브로, 공유 리소스에 대한 접근을 제어합니다. 비동기 프로그래밍에서 매우 유용하며 특정 리소스에 동시에 접근할 수 있는 코루틴 수를 제한할 수 있습니다. 다음 코드에서 볼 수 있듯이:
import asyncio import aiohttp async def fetch(url, session, semaphore): async with semaphore: print(f"Fetching {url}") async with session.get(url) as response: return await response.text() async def main(): urls = [ "http://example.com", "http://example.org", "http://example.net", "http://example.edu", "http://example.io", ] semaphore = asyncio.Semaphore(2) # 동시 요청 수를 2로 제한합니다. async with aiohttp.ClientSession() as session: tasks = [fetch(url, session, semaphore) for url in urls] responses = await asyncio.gather(*tasks) for url, response in zip(urls, responses): print(f"URL: {url}, Response length: {len(response)}") asyncio.run(main())
위의 코드에서 asyncio.Semaphore(2)
는 동시 요청 수를 2로 제한하기 위해 생성되었습니다. 비동기 함수 fetch
에서 async with semaphore
는 세마포어를 획득하고 해제하는 데 사용됩니다. 들어가기 전에 세마포어를 획득하기 위해 acquire()
메서드가 자동으로 호출되고 with
블록을 종료할 때 세마포어를 해제하기 위해 release()
메서드가 호출됩니다. Semaphore
를 사용하면 동시 요청 수를 제어하여 서버에 과도한 부하가 걸리는 것을 방지할 수 있습니다. 이는 데이터베이스 연결과 같이 제한된 리소스를 처리할 때 매우 유용합니다. 동시에 시스템 성능을 최적화하고 동시성의 균형을 찾을 수 있습니다.
Semaphore 원리
내부적으로 카운터를 유지 관리합니다. 카운터가 0보다 크면 접근이 허용되고, 0과 같으면 접근이 금지됩니다. 카운터를 획득하고 해제하는 방법은 각각 acquire()
및 release()
입니다. 초기화 중에 초기 카운터 값을 지정해야 합니다. 그런 다음 코드에서 카운터 값을 제어하여 동시 요청 수를 제어합니다.
멀티스레딩
멀티스레딩은 동시 작업 실행의 전통적인 방법이며 I/O 바운드 작업에 적합합니다. 다음 예제에서 볼 수 있듯이:
import threading import time def worker(name): print(f"Worker {name} starting") time.sleep(2) # 시간이 오래 걸리는 작업을 시뮬레이션합니다. print(f"Worker {name} finished") def main(): threads = [] for i in range(3): t = threading.Thread(target=worker, args=(i,)) threads.append(t) t.start() for t in threads: t.join() print("All workers finished") if __name__ == "__main__": main()
멀티스레딩의 t.join()
은 세 개의 스레드의 완료를 기다리는 데 사용됩니다. join()
메서드가 스레드 또는 프로세스 객체에서 호출되면 호출 스레드(일반적으로 주 스레드)는 join()
이 호출된 스레드 또는 프로세스가 실행을 마칠 때까지 차단됩니다.
멀티프로세싱
멀티프로세싱은 CPU 사용량이 많은 작업에 적합하며 아래와 같이 멀티 코어 프로세서를 최대한 활용할 수 있습니다.
import multiprocessing import time def worker(name): print(f"Worker {name} starting") time.sleep(2) # 시간이 오래 걸리는 작업을 시뮬레이션합니다. print(f"Worker {name} finished") if __name__ == "__main__": processes = [] for i in range(3): p = multiprocessing.Process(target=worker, args=(i,)) processes.append(p) p.start() for p in processes: p.join() print("All workers finished")
결론
위의 비동기 방법 외에도 Python에는 콜백 함수 또는 Gevent와 같은 타사 라이브러리를 사용하는 것과 같은 다른 비동기 접근 방식이 있습니다. 각 방법에는 고유한 장점과 제한 사항이 있습니다. 예를 들어 스레드는 I/O 바운드 작업에 적합하지만 GIL(Global Interpreter Lock)에 의해 제한됩니다. 멀티프로세싱은 CPU 사용량이 많은 작업에 적합하지만 메모리 오버헤드가 더 높습니다. 타사 라이브러리는 특수화된 기능과 최적화를 제공하지만 프로젝트의 복잡성을 증가시킬 수 있습니다. 대조적으로 async/await
구문은 보다 현대적이고 읽기 쉬운 비동기 프로그래밍 방법을 제공하며 현재 Python에서 비동기 작업을 처리하는 데 권장되는 방법입니다.
Leapcell: 최고의 서버리스 웹 호스팅
마지막으로 Python 서비스를 배포하는 데 가장 적합한 플랫폼인 **Leapcell**을 소개합니다.
🚀 좋아하는 언어로 빌드
JavaScript, Python, Go 또는 Rust로 손쉽게 개발하십시오.
🌍 무제한 프로젝트 무료 배포
사용한 만큼만 지불하십시오. 요청이나 요금이 부과되지 않습니다.
⚡ 사용한 만큼 지불, 숨겨진 비용 없음
유휴 요금이 없으며 원활한 확장성만 있습니다.
🔹 Twitter에서 팔로우하세요: @LeapcellHQ