상세 컨텐츠

본문 제목

파이썬 비동기 처리 작업 구현 - asyncio 라이브러리

개발/python-병렬처리

by Matthew0633 2022. 5. 12. 19:09

본문

asyncio 라이브러리

( 공식문서 : https://docs.python.org/3.7/library/asyncio.html )

  • Queue 기반으로 동작하는 병렬처리를 위한 라이브러리이다
  • 비동기 i/o 코루틴을 통한 동시 작업을 지원한다
  • async, await 의 keyword 를 사용할 수 있다
  • pip, conda를 통한 설치 필요

Blocking I/O: 호출된 함수가 자신의 작업이 완료될 때까지 제어권을 가진다. 타 함수는 대기
Non-Blocking I/O : 호출된 함수(서브루틴)이 return 후 (코루틴의 yield) 호출한 함수 (메인루틴) 에 제어권 전달, 또 다른 함수 작업 (서브루틴) 을 진행할 수 있다

asyncio 를 효과적으로 쓰려면?

내가 쓰려는 함수가 Blocking 으로 구현되어 있는지, Non-Blocking 으로 구현되어 있는지 확인해야한다. Blocking 이라면 single-thread로 실행하는 것이 빠르다

스레드와 코루틴 recap

  • 스레드의 단점 : 어려운 디버깅, 자원접근 시 race 컨디션 (경쟁상태, deadlock 발생 및 속도 저하 가능성)
  • 코루틴 활용한 동시성 장점 : 하나의 루틴만 실행하므로 lock 관리가 필요없이 제어권으로 코드 실행, 스레드의 단점 회피
  • 코루틴 활용한 동시성 단점 : 사용 함수가 비동기로 구현이 되어있거나, 직접 비동기로 구현해서 사용해야한다

asyncio 사용 예시

그렇다면 asyncio 로 5개의 웹페이지 html 을 크롤링하는 코드를 작성해보자

asnyc, await : 코루틴을 위해 for, yield keyword를 사용할 경우, 일반 Generator 와 구별이 힘들 수 있는데, asnyc, await keyword를 대신 사용하여 비동기 함수로 정의했음을 쉽게 나타낼 수 있다

메인 함수도 비동기, 서브루틴으로 실행할 함수들도 비동기식으로 구현해야한다

실행 영역에서는 asyncio 의 loop를 초기화하고, 작업의 메인루틴에 해당하는 함수를 실행시키면 끝이다

그럼 메인루틴에 해당하는 main() 를 들여다보자. 이 또한 asynckeyword를 통해 비동기 방식으로 작업을 수행하는 함수로 정의된다. 스레드풀을 생성하고 스케쥴링 목록을 생성 후 ( asyncio.ensure_future() ) 결과를 반환 ( asyncio.gather() ) 받도록 하면 된다

마지막으로, 스케쥴링 목록 생성하는 부분을 좀 더 들여다보자면, 서브루틴으로서 실행할 함수 (fetch)를 ensure_future() 의 인자로 넣어주는데 이또한 비동기식으로 정의해야한다. 서브루틴으로 호출될 함수 내에서 실제 작업할 함수를 loop.run_in_executor() 을 통해 실행시킨다

import asyncio
from urllib.request import urlopen
from concurrent.futures import ThreadPoolExecutor
import threading

# 게시판 커뮤니티
urls = ['http://daum.net', 'https://naver.com', 'http://mlbpark.donga.com/', 'https://tistory.com', 'https://wemakeprice.com/']

async def fetch(url, executor):
    # 실행
    res = await loop.run_in_executor(executor, urlopen, url)
    # print('Thread Name :', threading.current_thread().getName(), 'Done', url)

    # 결과 반환
    return res.read()

async def main():
	# 메인루틴

    # [스레드풀 생성]
    executor = ThreadPoolExecutor(max_workers=10)

    # [스케쥴링 목록 생성]
    futures = [ asyncio.ensure_future(fetch(url, executor)) for url in urls ]
    
    # [결과 생성]
    rst = await asyncio.gather(*futures)
    print('Result : ', rst)

if __name__ == '__main__':

    # [루프 초기화]
    loop = asyncio.get_event_loop()

    # [메인루틴 실행]
    loop.run_until_complete(main())

관련글 더보기

댓글 영역