파이썬에 내장된 비동기 라이브러리 asyncio 사용법
컨텐츠 정보
- 조회 730
본문
애플리케이션에서 파이썬의 비동기 프로그래밍 기능을 사용하면 각각의 독립적인 태스크가 끝나기를 기다리지 않고 따라서 더 많은 작업을 처리할 수 있다. asyncio 라이브러리는 디스크 또는 네트워크 I/O를 비동기적으로 처리하기 위해 파이썬에 포함된 툴 모음이다.
asyncio는 비동기 작업을 처리하기 위해 고수준과 저수준, 두 가지 종류의 API를 제공한다. 고수준 API는 가장 일반적으로 사용되며 가장 광범위한 애플리케이션에 적용 가능하다. 저수준 API는 강력하지만 복잡하고 사용 빈도도 상대적으로 낮다.
기사에서는 asyncio의 가장 자주 사용되는 고수준 API를 살펴보면서 비동기 태스크와 관련된 일반적인 작업에서 사용하는 방법을 시연한다.
파이썬에서 코루틴과 태스크 실행
가장 일반적인 asyncio의 용도는 당연히 파이썬 스크립트에서 비동기적인 부분을 실행하는 것이다. 이를 위해서는 코루틴과 태스크(task)를 다루는 방법을 알아야 한다.
코루틴과 태스크를 포함한 파이썬의 비동기 구성요소는 다른 비동기 구성요소와 함께 사용할 수 있고 일반적인 동기식 파이썬과는 함께 사용할 수 없으므로 이 간극을 메우기 위해 asyncio가 필요하다. 다음과 같이 asyncio.run 함수를 사용한다.
import asyncioasync def main(): print ("Waiting 5 seconds. ") for _ in range(5): await asyncio.sleep(1) print (".") print ("Finished waiting.") asyncio.run(main()) 이 코드는 main()과 함께 main()이 촉발하는 모든 코루틴을 실행하고 결과가 반환될 때까지 기다린다.
일반적으로 파이썬 프로그램에는 .run() 문이 하나만 있어야 한다. main() 함수가 하나만 있어야 하는 것과 마찬가지다. 비동기를 부주의하게 사용하면 프로그램의 제어 흐름이 읽기 어렵게 된다. 프로그램의 비동기 코드에 대한 진입점을 하나만 두면 복잡해지는 것을 방지할 수 있다.
비동기 함수는 태스크로 예약될 수도 있고, 코루틴을 래핑하고 실행하는 데 도움이 되는 객체로 예약될 수도 있다.
async def my_task(): do_something() task = asyncio.create_task(my_task())
이벤트 루프에서 my_task()가 실행되고 그 결과는 task에 저장된다.
태스크는 백그라운드에서 실행할 것을 설정한 다음 나중에 결과를 확인하려는 경우에 유용하다. 예를 들어 이 태스크를 목록에 저장하고 스케줄에 따라 또는 필요할 때 나중에 확인할 수 있다.
결과를 얻고자 하는 태스크가 하나뿐인 경우 asyncio.wait_for(task)를 사용해서 태스크가 완료될 때까지 기다린 다음 task.result()를 사용해 결과를 불러올 수 있다. 복수의 태스크를 실행하도록 예약했고 다른 작업을 계속 진행하기 전에 모든 태스크가 완료될 때까지 기다리려는 경우에는 asyncio.wait([task1, task2])를 사용해 결과를 수집한다. 특정 시간 이상 실행되지 않도록 하려면 작업에 대한 시간 제한을 설정할 수도 있다.
파이썬의 비동기 이벤트 루프 관리하기
asyncio의 또 다른 일반적인 용도는 비동기 이벤트 루프 관리다. 이벤트 루프는 비동기 함수와 콜백을 실행하는 객체로, asyncio.run()을 사용할 때 자동으로 생성된다. 관리 편의성을 위해서는 일반적으로 프로그램당 하나의 비동기 이벤트 루프만 사용하는 것이 좋다.
서버와 같이 더 고급 소프트웨어를 만드는 경우에는 이벤트 루프에 대한 하위 수준의 액세스가 필요하다. 이를 위해 “뚜껑을 열고” 이벤트 루프의 내부를 직접 조작할 수 있지만 간단한 작업에서는 굳이 그렇게 할 필요는 없다.
파이썬에서 스트림으로 데이터 읽기 및 쓰기
비동기가 가장 잘 맞는 시나리오는 다른 리소스가 결과를 반환할 때까지 기다리느라 애플리케이션이 차단될 수 있는 장기 실행 네트워크 작업이다. asyncio는 이와 관련해서 네트워크 I/O를 수행하기 위한 고수준 메커니즘인 스트림을 제공한다. 여기에는 네트워크 요청에 대한 서버 역할도 포함된다.
asyncio는 StreamReader와 StreamWriter, 2개의 클래스를 사용해서 높은 수준에서 네트워크에서 읽고 쓴다. 네트워크에서 읽으려면 asyncio.open_connection()을 사용해 연결을 연다. 이 함수는 StreamReader와 StreamWriter 객체의 튜플을 반환하는데, 각각의 .read()와 .write() 메서드를 사용해 통신한다.
원격 호스트로부터 연결을 수신하려면 asyncio.start_server()를 사용한다. asyncio.start_server() 함수는 요청이 수신될 때마다 호출되는 콜백 함수 client_connected_cb를 인수로 취한다. 이 콜백 함수는 StreamReader와 StreamWriter의 인스턴스를 인수로 취하므로 서버에 대한 읽기/쓰기 로직을 처리할 수 있다. (asyncio 기반 aiohttp 라이브러리를 사용하는 간단한 HTTP 서버 예시는 여기에서 확인할 수 있다.)
파이썬에서 태스크 동기화하기
비동기 태스크는 대체로 격리된 상태로 실행되지만 태스크 간 통신이 필요한 경우가 종종 있다. asyncio는 태스크 간 동기화를 위한 몇 가지 메커니즘을 제공한다.
- 큐 :
asyncio큐는 비동기 함수가 예를 들어 동작에 따라 다양한 종류의 함수 간에 워크로드를 분산하기 위해 다른 비동기 함수들이 소비할 파이썬 객체를 정렬할 수 있게 해준다. - 동기화 프리미티브 :
asyncio의 잠금, 이벤트, 조건, 세마포어는 각각에 대한 일반적인 파이썬 항목과 비슷하게 작동한다. 예를 들어 더 큰 조건이 충족될 때까지 기다리거나 한 번에 하나의 엔터티에만 사용 가능한 리소스에 대한 제어 권한을 획득하는 경우와 같이 여러 태스크 간에 다른 종류의 활동을 조율하기 위한 용도로 사용된다.
유의해야 할 점은 이러한 방법은 모두 스레드 안전성이 없다는 것이다. 이는 동일한 이벤트 루프에서 실행되는 비동기 태스크에서는 문제가 되지 않지만 다른 이벤트 루프, 운영체제 스레드 또는 프로세스의 태스크와 정보를 공유하려는 경우에는 threading 모듈과 해당 객체를 사용해야 한다.
또한 스레드 경계를 넘어 코루틴을 실행하려면 asyncio.run_coroutine_threadsafe() 함수를 사용하고 이 함수와 함께 사용할 이벤트 루프를 매개변수로 전달한다.
파이썬에서 코루틴 일시 정지하기
자주 논의되지는 않지만 asyncio의 또 다른 일반적인 용도는 코루틴 내에서 임의의 시간 동안 기다리는 것이다. time.sleep()의 경우 프로그램 전체가 차단되므로 사용할 수 없다. 따라서 다른 코루틴이 계속 실행되도록 허용하는 asyncio.sleep()을 사용한다.
외부 조건을 계속 기다리기 위해 루프에서 asyncio.sleep()을 사용하려는 생각을 하고 있다면 그 생각은 버리는 것이 좋다. 이론적으로 가능하긴 하지만 깔끔하지 않기 때문이다. 그보다는 asyncio.Event 객체를 태스크에 전달해서 Event 객체가 변경될 때까지 기다리는 방법이 더 낫다.
비동기와 파일 I/O
위에서 설명한 바와 같이 비동기 네트워크 I/O의 경우 차단하지 않도록 만들 수 있지만 로컬 파일 I/O는 기본적으로 현재 스레드를 차단한다. 한 가지 해결 방법은 asyncio.to_thread()를 사용해서 파일 I/O 작업을 다른 스레드에 위임해 이벤트 루프의 다른 태스크가 처리될 수 있도록 하는 것이다.
파일 I/O를 비동기적으로 처리하는 또 다른 방법은 서드파티 aiofiles 라이브러리를 사용하는 방법이다. 이 경우 예를 들어 async with aiofiles.open("myfile.txt") as f:와 같이 파일을 열고 읽고 쓰기 위한 고급 비동기 구조가 제공된다. 이를 종속 항목으로 프로젝트에 넣어도 관계없다면 이 문제를 깔끔하게 해결하는 방법으로 사용할 수 있다.
파이썬에서 하위 수준 비동기 사용하기
마지막으로, 현재 구축 중인 앱에 asyncio의 하위 수준 구성요소가 필요할 수 있다고 판단되는 경우 코딩을 시작하기 전에 주변을 살펴보는 것이 좋다. 필요한 작업을 처리하는 비동기 기반 파이썬 라이브러리를 다른 누군가가 이미 만들어 두었을 가능성이 높기 때문이다.
예를 들어 비동기 DNS 쿼리가 필요하다면 aiodns 라이브러리가 있고, 비동기 SSH 세션 용도로는 asyncSSH가 있다. “async” 및 태스크 관련 다른 키워드로 파이파이를 검색하거나 수동으로 선별된 ‘어썸 Asyncio’ 목록에서 아이디어를 확인할 수 있다.
dl-itworldkorea@foundryco.com
관련자료
-
링크
-
이전
-
다음






