Speed Up Your Python Program With Concurrency

정은경·2020년 7월 12일
0

다음의 내용은 "real python" 사이트의 "Speed Up Your Python Program With Concurrency" 아티클을 번역/정리한 내용입니다.

이 아티클에서는 아래의 것들을 알게된다:

  • concurrency(동시성)는 무엇인가
  • parallelism(병행성)은 무엇인가
  • 파이썬 concurrency 메소드를이 서로 어떻게 다른지 비교 (threading/asyncio/multiprocessing)
  • 언제 concurrency를 사용하고, 사용한다면 어떤 동시성 모듈을 사용해야 하는가

1. Concurrency(동시성)이란 무엇인가?

동시성의 사전적 정의: 동시에 발생함

파이썬에서, "동시에 발생하는 것"을 부르는 다양한 이름들(thread, task, process)이 있다.
하지만 고차원에서 보면,
"thread, task, process" 등의 동시에 발생하는 것들은 "순서대로 동작하는 명령어들의 순서를 의미"한다.

"동시에 발생하는 것들"은 서로 다른 생각의 기차들과 같다.
각각은 특정 포인트에서 멈추고, (멈추면 cpu 또는 두뇌가 그것들을 프로세싱함)
각각은 다른 것들로 스위치(전환)할 수 있고,
각각의 상태는 저장되기 때문에, interrupt되었던 시점에 저장되었던 상태에서 다시 시작할 수 있다.

"동시적(simultaneous)"이라는 의미를 살펴보자.
자세히 살펴보면, 오직 "멀티프로세싱"만이 진짜로 실질적으로 말그대로 동.시.에. 실행된다.
"threading"과 "asyncio"는 둘다 "싱글" 프로세서라서 한번에 하나가 실행된다.
threading과 asyncio는 전반적은 프로세스의 속도를 높이기 위해서 번갈아 실행하는 영리한 방법을 찾았다.
비록 threading과 asyncio가 동시에 실행되는 것은 아니지만, 여전히 동시성(concurrency)라고 부른다.

thread 또는 task가 번걸아가면서 실행되는 방법은
threading일 때와 asyncio일 때와 큰 차이가 있다.

threading일 때는,
thread가 언제든 운영체제를 interrupt해서 다른 thread를 실행하도록 할 수 있음을 운영체제는 알고 있다.
이를 pre-emptive multitasking(선점형 멀티태스킹)이라고 한다.
왜냐하면 os가 switch(문맥전환)를 하기위해서 당신의 thread를 선점할 수 있기 때문이다.
pre-emptive multitasking(선점형 멀티태스킹)은 switch(문맥전환)을 할 필요가 없다는 점에서 thread 코드 상에서 간단하다.
pre-emptive multitasking(선점형 멀티태스킹)는 어렵다. 왜냐하면 "at any time(언제든지)"라는 부분을 만족해야해서.
switch(문맥전환)은 파이썬 코드 단 1줄을 실행하는 와중에도 발생할 수 있다.

"Asyncio" 는 반면,
협동적 다중 작업(cooperative multitasking)을 사용한다.
task들을 협력해야한다.
언제 switch(문맥전환)할 준비가 되었는지 알려줌으로써 서로 협력해야한다.
이는 task의 코드가 이를 가능하도록 약간의 수정이 필요함을 의미한다.
위의 내용을 앎으로써의 장점은
언제 당신의 task가 swapped out 될지 항상 알 수있게 된다는 점이다.
이것은 명령문이 표시(marked)가 되지 않는한, 파이썬 명령문의 중간에서 스와아웃되지는 않는다.

2. Parallelism(병행성)이란 무엇인가?

위에서 언급된 동시성은 싱글 프로세서일때!
만약, 프로세서가 여러개라면?!
멀티프로세싱이 답이다.

멀티프로세싱을 가지고, 파이썬은 새로운 '프로세스'들을 생성한다.
여기서 말하는 '프로세스들'이란 서로다른 프로그램을 의미한다.
프로세스는 보통 "메모리, 파일과 같은 자원들의 모음"으로 정의된다.
각각의 프로세스는 각각의 고유한 파이썬 인터프리터에서 실행된다.

왜냐하면, 다양한 프로세스들이 있기 때문이다.
각각의 멀티프로세싱 프로그램의 각각의 당산의 생각열차들은 서로다른 코어(cpu)에서 실행할 수 있다.
다른 코어에서 실행된다는 말은, 각각이 서로 다른 코어에서 동시에 실행될 수 있다는 말이다.
이것을 함에는 복잡함이 있지만 파이썬은 복잡함을 줄이려고 노력하고 있다.

3. 언제 동시성이 유용할까?

동시성은 크게 2가지 유형의 문제를 만든다.
1) Cpu-bound
2) i/o-bound

I/O-bound 문제는 당신의 프로그램을 느리게 만드는 원인이 될 수 있다.
왜냐하면, 외부 자원(파일시스템, 네트워크 커넥션)으로부터 input/output(I/O)를 자주 기다려야하기 때문이다.

CPU-bound 프로그램은, 프로그램의 속도를 좌지우지하는 것이 CPU이다.

Concurrency를 적용할 때는 적용할 프로그램이 I/O-bound인지, CPU-bound인지 따져볼 필요가 있다.
왜냐하면, bound 타입에 따라서 적합한 concurrency 종류가 있다.
concurrency를 적용하는 것이 프로그램에 추가적인 코드와 복잡성을 주기 때문에,
concurrency를 적용하는 노력과 잠정적인 속도향상의 가치를 따져볼 필요가 있따.

4. I/O-bound 프로그램 속도향상법

I/O-bound 프로그램과 일반적인 문제: "네트워크를 통해서 콘텐츠 다운받기"
아래의 예제에서는, 몇몇의 사이트에서 웹페이지를 다운받는 예제를 만든다.

4-1. Synchronous 버전 예제

왜 동기적인 버전이 좋은가

이해하기 쉬움

동기적 버전의 문제

다음의 다른 방법들에 비해 상대적으로 느림

4-2. threading 버전 예제

  • ThreadPoolExecutor = Thread + Pool + Executor
  • Python 3.2부터 Executor가 생김
  • session은 thread-safe하지 않음 (https://github.com/requests/requests/issues/2766)
  • 세션은 thread-safe하지 않아서 각각의 쓰레드를 위한 개별적인 세션이 필요함
  • Threading.local()는 각각 스레드별로 개별적인 오브젝트임
  • thread-safe하다는 말은 쓰레드끼리 쉐어하는 데이터가 보호됨을 의미

데이터 접근을 thread-safe하도록 만드는 전략들:
1) thread-safe한 자료구조를 갖는 queue 모듈을 사용한다
Queue는 thread-safe한 자료구조를 가지고 있음
2) "threading.Lock"으로 한번에 하나의 쓰레드만이 메모리에 접근하도록 하는 원시적인 방법이 있음
3) local storage 사용하기
"Threading.local()"은 글로벌로 보이지만, 각각의 쓰레드에 국한?된다

왜 threading 버전이 좋은가

빠르다!

threading 버전의 문제

  • 무슨 데이터가 쓰레드 간에 공유되어야 하는 가 생각해보아야 함
  • 미묘하거나 감지하기 어려운 방법들로 쓰레드들은 서로 상호작용한다.
  • race condiation 유발 가능성 있음

Race Conditions

4-3. asyncio 버전 예제

  • asyncio는 어떻게 동작하는가?
    - 기본개념은 "event loop"라고 불리는 파이썬 오브젝트가 각각의 task들이 언제 어떻게 실행해야하는 지 컨트롤하는 것을 의미한다.
    - "event loop"는 각각의 task를 인지하고, 각각의 task가 어떤 state인지 안다.
    - asyncio에서 중요한 부분은 task는 절대 control를 포기하기 않는다.
    - task는 동작중에 인터럽트 당하지 않는다.
    - 그래서 thread-safe를 위한 코드를 별도로 작성하지 않아도 된다.

How does asyncio actually work?
https://stackoverflow.com/questions/49005651/how-does-asyncio-actually-work/51116910#51116910

  • async와 await
    - await: task가 다시 event loop에 제어를 넘기는 것

asyncio 예제코드

asyncio 좋은 점

빠르다!

asyncio 나쁜 점

asyncio를 충족하게 쓰려면 특별한 asyncio 라이브러리를 써야함

4-4. multiprocessing 버전 예제

Global Interpreter Lock (GIL)
https://realpython.com/python-gil/

multiprocessing의 좋은 점

설정이 슆고 추가 코드가 작음

multiprocessing의 문제 점

각 프로세스에서 어떤 부분을 담당하는지 생각해보아야 함

io-bound 결론:

I/O-bound는 대부분의 시간을 외부 동작을 기다리는데 사용함

5. CPU-bound 프로그램 속도향상법

io-bound와 다르게
cpu-bound에는 기다림이 없다

5-1. Synchronous 버전 예제


5-2. threading과 asyncio 버전

5-3. multiprocessing 버전 예제

multiprocessing 버전의 장점

설정이 쉽고 추가코드가 작음
cpu 파워를 full로 사용가능

multiprocessing의 단점:

프로세서 간의 커뮤니케이션이 많이 요구됨
개별적인 프로세서의 문제를 판단하기 어려움

6. 언제 동시성을 사용하는가

1) 동시성 모듈을 사용해야 하는가?
- 성능상의 문제가 발견되기 전까지는 결정을 미루기
- 어떤 동시성 타입이 판단되기 전까지는 결정을 미루기
2) 프로그램이 cpu-bound인지 io-bound인지 알아보기
- cpu-bound는 오직 멀티프로세싱이 좋음
- io-bound는 asyncio

7. 결론

Reference

profile
#의식의흐름 #순간순간 #생각의스냅샷

0개의 댓글