파이썬 중급, 인프런 리프 #4. 파이썬 병행성, 비동기 AsyncIO 멀티 스크랩핑 실습 후기
안녕하세요. 인프런 리프 2기 다람쥐입니다.
네 번째 주차에는 파이썬 병행성과 비동기 AsyncIO 멀티 스크랩핑을 공부했어요.
파이썬 병행성 강의에선 많은 것을 배웠는데요,
먼저, 첫 강의에선 병행성(Concurrency) 이라는 개념과 더불어 이터레이터(Iterator)와 제너레이터(Generator)를 소개했어요.
이터레이터와 제너레이터를 실습해보고 어떤 차이점이 있는지 배웠어요~
그 다음 강의에선 코루틴(Coroutine) 개념을 배웠어요.
병행성을 처음 접하는 분들에겐 생소한 개념이다 보니 많은 신경을 써서 천천히 알려주신 점이 인상 깊었네요!
코루틴과 서브루틴의 차이점과 쓰레드는 무엇인지, 코루틴이 쓰레드보다 어떤 점이 나은지, 왜 나은지를 알 수 있었어요!
그 다음 강의에선 concurrent 패키지의 futures 유틸 함수를 익혀 멀티스레딩 / 멀티프로세싱 둘 다 지원하는 API 를 사용해봤어요~
파이썬 언어에서 어떻게 두 방식을 추상화했는지 쉽게 알 수 있었네요.
파이썬 스레드 관련해서 항상 나올 수 밖에 없는 파이썬 GIL (Global Interpreter Lock) 도 소개해줬어요~
마지막 AsyncIO 멀티 스크랩핑 강의에선 asyncio 패키지를 직접 실습하여 병행성을 제공하는 프로그램을 작성해봤어요~
기존 파이썬 프로그램 작성법과 어떤 점이 다른지 중심으로 공부했어요!
블록킹 방식과 논블록킹 방식의 차이를 알고 코루틴의 장단점을 알 수 있었어요.
병행성 강의와 실습 강의를 들어보고 느낀점은,
병행성과 관련하여 내용도 방대한데, 중요한 점만 콕콕 찝어 빠르고 쉽게 알 수 있었던 것 같네요~
※ 본 리뷰는 인프런, 우리를 위한 프로그래밍 : 파이썬 중급 (Inflearn Original) 강의를 리뷰하였어요.
[진도표]
우리를 위한 프로그래밍 : 파이썬 중급 1주차
- 파이썬 중급 소개 & 커리큘럼
- 파이썬 클래스 심화
- 파이썬 데이터 모델
- 파이썬 시퀀스
- 파이썬 일급 함수
- 파이썬 병행성 ← 현재
- 최종 실습 - AsyncIO 멀티 스크래핑 실습 ← 현재
6. 파이썬 병행성
처음으로 이터레이터와 제너레이터를 정리했어요~
이터레이터는 값을 차례대로 꺼낼 수 있는 객체라고 하네요.
__next__ 매직 메서드를 구현하여 next() 함수를 호출할 때마다 다음 값을 꺼내올 수 있도록 할 수 있어요.
반복을 종료할 때는 StopIteration 예외를 발생시켜서 멈춘다고 하네요.
class WordSplitter:
def __init__(self, text):
self._idx = 0
self._text = text.split(' ')
def __next__(self):
# print('Called __next__')
try:
word = self._text[self._idx]
except IndexError:
raise StopIteration('Stopped Iteration.')
self._idx += 1
return word
def __repr__(self):
return 'WordSplit(%s)' % (self._text)
wi = WordSplitIter('Do today what you could do tomorrow')
print(wi)
print(next(wi))
print(next(wi))
print(next(wi))
print(next(wi))
print(next(wi))
print(next(wi))
print(next(wi))
# print(next(wi))
제너레이터는 특별하게 yield 키워드로 손쉽게 다음 값을 받을 수 있는데요,
__iter__ 매직 메서드를 구현해서 반복 가능한 객체를 만들어볼 수 있어요.
__next__ 매직 메서드를 구현하지 않아도 iter() 함수로 반복 가능하도록 만들 수 있다고 하네요.
class WordSplitGenerator:
def __init__(self, text):
self._text = text.split(' ')
def __iter__(self):
# print('Called __iter__')
for word in self._text:
yield word # 제네레이터
return
def __repr__(self):
return 'WordSplit(%s)' % (self._text)
wg = WordSplitGenerator('Do today what you could do tomorrow')
wt = iter(wg)
print(wt)
print(next(wt))
print(next(wt))
print(next(wt))
print(next(wt))
print(next(wt))
print(next(wt))
print(next(wt))
# print(next(wt))
제너레이터는 저번 포스팅에도 보았지만, 데이터 양이 증가하다보면 컨테이너에 저장해야 할 메모리 사용량이 늘어나는데요~
제너레이터를 사용하면 한 번에 한 메모리 공간으로 불러들어와 처리를 할 수 있어요.
대용량 처리 시에 제너레이터를 사용하는 걸 권장한다고 하네요.
또 단위 실행이 가능한 코루틴(Coroutine) 이라는 거를 구현할 수 있고 연동도 편리하다고 하네요.
제너레이터는 리스트 컴프리헨시브 형태에서 대괄호( [] )를 괄호( () ) 로 바꾸면 돼요.
손쉽게 제너레이터를 만들 수가 있네요.
temp2 = [x * 3 for x in generator_ex1()]
temp3 = (x * 3 for x in generator_ex1())
print(temp2)
print(temp3)
itertools 패키지에 있는 여러 편리할 이터레이터 함수를 제공을 해주는데요~
takewhile, filterfalse, accumulate, chain, product, groupby 등을 실습해볼 수 있어서 좋았어요.
gen2 = itertools.takewhile(lambda n : n < 1000, itertools.count(1, 2.5))
gen3 = itertools.filterfalse(lambda n : n < 3, [1,2,3,4,5])
gen4 = itertools.accumulate([x for x in range(1, 101)])
gen5 = itertools.chain('ABCDE', range(1,11,2))
gen6 = itertools.chain(enumerate('ABCDE'))
gen7 = itertools.product('ABCDE')
gen8 = itertools.product('ABCDE', repeat=2)
gen9 = itertools.groupby('AAABBCCCCDDEEE')
다음으론 코루틴(Coroutine)을 배웠어요!
yield 를 사용해 메인과 서브 간에 값을 주고받을 수가 있는데요~
여러가지 루틴(routine)을 실행할 수 있고 루틴 실행 중에 중지하고 다른 루틴을 실행하는 식으로 흐름을 제어할 수가 있다고 합니다~
쓰레드는 공유되는 자원이 많을수록 서로 다른 스레드가 서로를 대기하는 교착 상태(Deadlock, 데드락)이 발생할 수 있고, 컨텍스트 스위칭 비용이 발생하고, 자원 소비 가능성이 증가하는 특징이 있어요.
코루틴은 그런 쓰레드에 비해 오버헤드가 감소하는 장점이 있어요!
# 코루틴 Ex2
# GEN_CREATED : 처음 대기 상태
# GEN_RUNNING : 실행 상태
# GEN_SUSPENDED : yield 대기 상태
# GEN_CLOSED : 실행 완료 상태
def coroutine2(x):
print('>>> coroutine started : {}'.format(x))
y = yield x
print('>>> coroutine received : {}'.format(y))
z = yield x + y
print('>>> coroutine received : {}'.format(z))
cr3 = coroutine2(10)
from inspect import getgeneratorstate
print(getgeneratorstate(cr3))
print(next(cr3))
print(getgeneratorstate(cr3))
print(cr3.send(15))
코루틴을 직접 실행해가며 상태까지 로그를 찍어가며 배우니 동작 원리가 머리에 잘 남네요~
다음은 concurrent.futures 모듈에 있는 ThreadPoolExecutor, ProcessPoolExecutor, wait, as_completed 를 사용하며 병렬 프로그래밍을 경험해봤어요.
# ProcessPoolExecutor
with ThreadPoolExecutor() as excutor:
for work in WORK_LIST:
# future 반환
future = excutor.submit(sum_generator, work)
# 스케쥴링
futures_list.append(future)
# 스케쥴링 확인
print('Scheduled for {} : {}'.format(work, future))
# print()
for future in as_completed(futures_list):
result = future.result()
done = future.done()
cancelled = future.cancelled
# future 결과 확인
print('Future Result : {}, Done : {}'.format(result, done))
print('Future Cancelled : {}'.format(cancelled))
멀티 스레드와 멀티 프로세스를 구현하는 API 가 통일되어 손쉽게 사용할 수 있다는 점이 인상 깊었네요.
future 의 개념과 각 작업이 끝났을 때 어떻게 결과를 확인하는 지 직접 결과를 쳐보며 알 수 있었어요!
7. 최종 실습 - AsyncIO 멀티 스크랩핑 실습
비동기 패키지로 유명한 asyncio 패키지와 concurrent.futures 패키지의 ThreadPoolExecutor, beautifulsoup4, 코루틴을 사용하여 스크랩핑을 하는 프로그램을 작성해 봤어요.
열심히 노트에 메모를 해가며 패키지 설치를 메모하거나 어려운 개념들을 정리해봤어요!
영상을 보며 바로 우측에 노트를 작성할 수 있다는 점이 꽤 편리했네요.
코드를 보면 이전에 보지 못한 키워드가 많았는데요~
생소한 async, await 키워드를 익혀보고 두 키워드를 왜 써야 하는지도 차근차근 설명해주어서 인상 깊었어요!
어렵다고 느꼈던 비동기, 병렬 프로그래밍도 이번에 실습을 같이 해보면서 많이 진입 장벽이 허물어진 느낌이 들어서
뿌듯한 학습이었습니다!
async def fetch(url, executor):
# 실행
res = await loop.run_in_executor(executor, urlopen, url)
...
async def main():
# 쓰레드 풀 생성
executor = ThreadPoolExecutor(max_workers=10)
...
# future 객체 모아서 gather에서 실행
futures = [
asyncio.ensure_future(fetch(url, executor)) for url in urls
]
...
# 결과 취합
rst = await asyncio.gather(*futures)
if __name__ == '__main__':
# 루프 초기화
loop = asyncio.get_event_loop()
# 작업 완료 까지 대기
loop.run_until_complete(main())
...
# 수행 시간 계산
duration = timeit.default_timer() - start
# 총 실행 시간
print('Total Running Time : ', duration)
본 리뷰는 인프런, 우리를 위한 프로그래밍 : 파이썬 중급 (Inflearn Original) 강의를 리뷰하였어요.
독자분들도 같의 강의를 듣고 서로 모르는 부분은 이야기 해보면 좋을 것 같아요.
다 같이 성장할 수 있도록 저도 열심히 학습하겠습니다!
벌써 마지막 강의까지 후기를 달았네요.
시간을 쪼개가면서 강의를 들었던 만큼 절박한(?) 마음으로 들어서 공부가 많이 됐었네요.
실무에서도 배운 내용을 꼭 적용해보도록 해보겠습니다. :)
다음 시리즈 글은 후기로 찾아뵙겠습니다!
- 인프런 리프 2기 시리즈 글
1. 파이썬 중급, 인프런 리프 #1. 파이썬 환경 설정 후기
2. 파이썬 중급, 인프런 리프 #2. 파이썬 클래스 심화, 데이터 모델 후기
3. 파이썬 중급, 인프런 리프 #3. 파이썬 시퀀스, 파이썬 일급 함수 후기
4. 파이썬 중급, 인프런 리프 #4. 파이썬 병행성, 비동기 AsyncIO 멀티 스크랩핑 실습 후기 <- 현재
5. 파이썬 중급, 인프런 리프 #5. 인프런 리프 2기 활동 후기