본문 바로가기
IT/Internals

[번역] Speed Up Your Python Program With Concurrency Part 1

by 물통꿀꿀이 2019. 8. 3.

파이썬 동시성에 관한 좋은 글이 있어서 관련 내용을 번역해보려 한다. 

*의역이 있을 수 있고 이해한 바를 바탕으로 정리했기 때문에 원문과 의미가 조금 달라 질 수 있다. (원문은 아래에 첨부하였다.)

https://realpython.com/python-concurrency/#how-to-speed-up-an-io-bound-program

추가로 해당 포스팅은 부분 부분 나누어서 진행하려고 한다.


동시성(Concurreny) 이란?

동시성의 사전적 정의는 어떠한 사건이 동시에 발생(simultaneous occurrence)하는 것이다. Python에서는 동시에 발생하는 사건들이 내부적으로 Thread, Task, Process 등에 의해 호출된다. (하지만 High level 관점에서는 모두 순서대로 수행되는 것들이다.) 또한 Thread, Task, Process 모두 특정 지점에서 멈출수 있고 처리 중인 CPU 등등은 다른 지점으로 Switching 할 수 있다. 특히 중단된 상태는 저장되기 때문에 각각 중단된 위치에서 다시 시작할 수 있다.


그러면 왜 Python에서는 같은 개념에 대해 다른 단어를 사용하는지 궁금할 것이다. 앞으로 다룰 몇몇 예시를 통해 확인 할 수 있겠지만 Thread, Task, Process은 High level 관점에서는 동일하지만 세부적으로는 조금씩 다르기 때문이다.


그럼 앞서 언급했던 동시적인 부분에 대해 알아보자. "동시적인" 이라는 단어에 대해 조금 주의해야한다. 그 이유는 사실상 여러 기법 중 Multiprocessing 만이 말 그대로 동시에 여러 작업을 수행하기 때문이다. 반면에 Threading, Asyncio는 단일 프로세서에서 동작하며 한 번에 하나씩만 실행한다. 그러나 동시성 기법 범주에 Threading과 Asyncio가 포함되어 있는 이유는 전체적으로 프로세스의 속도를 높이기 때문이다.


Thread(또는 Task)가 작업을 수행하는 방식은 Threading과 Asyncio의 큰 차이점이다. Threading은 OS가 각 Thread를 어느 시점*에든 중단시키고 다른 Thread를 실행할 수 있다. 이 기법은 OS가 Switching을 하기 위해 현재 동작하는 Thread를 선점 할 수 있기 때문에 선점형 멀티태스킹(pre-emptive multitasking)이라 한다. 선점형 멀티태스킹은 Switching을 하기 위해 Thread의 내부 코드에서 추가 작업을 할 필요가 없기에 매우 편리하다. (즉, Application 코드에서 작업할 부분이 없다.) 

*"어느 시점" 이라는 구문이 햇갈릴 수 있는데 말 그대로 선점형 멀티태스킹에서는 심지어 단일 문법인 x = x + 1에서도 선점할 수 있다.


반면에 Asyncio는 협력적 멀티태스킹(cooperative multitasking)이다. 이 것은 Task가 Switching 할 준비가 되어있음을 알리는 것으로 약간의 코드 변경이 필요하다. 추가적인 작업으로 Task는 어디에서 Swap out 해야 할지 알게 된다. 만약 코드 상에서 표기된 부분이 없다면 Python에서 수행되는 작업 중간에 Swap out되지 않는다. 이 부분은 나중에 확인 할 수 있다.


병렬(Parellelism) 이란?

이제까지 단일 프로세서에서 일어나는 동시성에 대해 살펴보았다. 그렇다면 단일 프로세서가 아닌 여러 개의 CPU core에서는 어떻게 사용할 수 있을까? 바로 Multiprocessing이 그 해답이 될 수 있다.


Python는 Multiprocessing을 기반으로 새로운 프로세스를 만들 수 있다. 프로세스는 거의 다른 프로그램으로 생각하면 된다. (물론 기술적으로는 Memory, File handle 등등을 포함하는 리소스의 모음이다.) 예를 들어 실행된 Python Interpreter는 하나의 프로세스이다.


Multiprocessing 프로그램에서 각 프로세스는 서로 다른 Core에서 실행된다. 다른 Core 에서 실행된다는 의미는 말 그대로 동시에 여러 프로세스가 실행될 수 있다는 것이다. 그러나 Multiprocessing 작업으로 인해 복잡성이 증가하지만 Python에서는 어렵지 않게 해결 할 수 있다.


지금까지 동시성과 병렬에 대해 알아보았다. 간단히 두 가지 개념에 대한 차이를 살펴보고 이후에 각각 유용하게 사용할 수 방법에 대해 알아보겠다.


Concurrency Type 

Switching Decision 

Number of Processors 

Threading (선점형 멀티태스킹)

OS가 언제 Task를 Switching 할 지 결정한다.

1

Asyncio (협력적 멀티태스킹)

Task가 제어권을 언제 포기 할 지 결정한다.

1

Multiprocessing

동시간에 모든 프로세스가 다른 프로세서에서 동작한다.

Many


언제 동시성이 유용한가?

동시성은 CPU bound와 I/O bound로 알려진 두 가지 문제에 대해 큰 차이를 만들 수 있다. 

먼저 I/O bound 문제는 외부 리소스에서 I/O를 자주 대기해야하기 때문에 프로그램을 느려지게 만든다. 보통 이러한 I/O bound 문제는 CPU 보다 더 느린 작업을 할 때 자주 발생한다. 물론 CPU 보다 느린 리소스들은 많이 있지만, 다행히도 보통 빈번하게 사용하는 리소스는 파일 시스템과 네트워크 연결이다. (프로그램이 대다수의 요소들을 사용하는 것은 아니다.)



위 그림을 살펴보면, 파란색 박스는 프로그램이 작업하고 있는 시간을 보여준다. 그리고 빨간 박스는 I/O 작업이 끝날 때까지 대기하는 시간이다. 결과적으로 프로그램은 대부분의 시간을 대기하는데 보낸다. 다시 말해서 인터넷 상의 요청인 브라우저가 대부분의 시간을 사용한다.


반면에, 네트워크 또는 파일 시스템 접근 없이 컴퓨팅 작업을 하는 프로그램이 있다. 해당 프로그램은 CPU bound 프로그램으로 프로그램의 속도를 제한하는 것은 네트워크도 파일 시스템도 아닌 CPU 이기 때문이다.


CPU bound 프로그램에 관한 그림을 살펴보자.


이후 섹션에서 여러 예제를 살펴보면서 CPU bound와 I/O bound 프로그램에서 동시성이 좋은지 살펴볼 것이다. 프로그램에 동시성을 추가하는 것은 코드가 더 늘어나고 복잡성 또한 높아지기 때문에 잠재적인 속도 증가가 추가 노력 대비 가치가 있는지 없는지 결정할 필요가 있다. (이 부분은 해당 포스팅의 말미에 얻을 수 있다.)


그럼 이제까지 살펴본 2 가지 문제(I/O Bound 및 CPU Bound) 에 대해 요약한 것을 살펴보면 아래와 같다.

I/O-Bound Process 

CPU-Bound Process 

프로그램은 대부분의 시간을 네트워크 연결, 하드 드라이브, 프린터와 같이 느린 장치와 통신하는데 보낸다.

프로그램은 대부분의 시간을 CPU 작업을 하는데 보낸다.

속도를 높이기 위해서는 장치 대기 시간을 겹쳐야 (Overlapping) 한다.

속도를 높이려면 같은 시간에 더 많은 계산을 할 수 있는 방법을 찾아야 한다.


먼저 I/O-bound 프로그램을 살펴볼 것이다. 여기서 CPU-bound 프로그램을 다루는 몇몇 코드도 함께 살펴볼 것이다.

댓글