- 상속과 다형성
동시성과 병렬성의 핵심 이해
복잡한 애플리케이션이나 성능이 중요한 프로그램을 만들다 보면, 여러 작업을 동시에 처리하는 방법이 필수가 된다. 파이썬의 중급 개발자라면 동시성(concurrency)과 병렬성(parallelism)의 차이부터 명확히 구분해야 한다. 동시성은 여러 작업이 겉보기에는 동시에 진행되는 것처럼 스케줄링되는 상황이고, 병렬성은 실제로 여러 작업이 동일 시점에 물리적으로 동시에 실행되는 것이다. 모든 병렬 처리는 동시성을 포괄하지만, 모든 동시 처리가 반드시 병렬적인 것은 아니다.
스레드와 멀티프로세싱의 활용
파이썬에서 동시성을 구현하기 위해 스레드(thread)와 프로세스(process)를 사용할 수 있다. 스레드를 이용하면 하나의 프로세스 내에서 여러 작업을 거의 동시에 실행하는 것처럼 분할할 수 있다. 예를 들어, 사용자 인터페이스와 데이터 처리가 분리되어야 할 때 활용된다. 이를 위해 파이썬의 threading
모듈을 사용한다. 하지만 파이썬에는 GIL(Global Interpreter Lock)이라는 제한이 있어, 순수 계산 위주의 작업에는 스레드의 효과가 제한적이라는 점을 기억해야 한다.
반면, 멀티프로세싱(multiprocessing)은 각 작업을 별도의 프로세스로 분리한다. 이 방식은 진짜로 병렬 실행이 가능하므로, CPU를 많이 사용하는 연산(이미지 처리나 대규모 데이터 분석 등)에 유리하다. multiprocessing
모듈은 각각의 프로세스가 독립된 메모리 공간을 가지므로, 한쪽에서 발생한 에러가 전체에 영향을 미치지 않는다. 멀티코어 프로세서를 최대한 활용하고 싶다면 멀티프로세싱이 적합하다.
async/await와 비동기 프로그래밍
스레드와 프로세스는 무겁고 복잡할 수 있어서, I/O 중심의 작업(네트워크, 파일 입출력 등)에는 비동기 프로그래밍이 훨씬 더 효율적이다. 파이썬 3.5 이후부터는 async
와 await
키워드를 통해 간결하고 직관적으로 비동기 코드를 작성할 수 있다.
async def
로 정의된 함수는 코루틴(coroutine)이 되고, 이 안에서 시간이 오래 걸리는 작업이나 외부 리소스 요청에 await
를 붙여 실행하면, 해당 작업이 완료될 때까지 다른 작업이 진행될 수 있다. 예를 들면, 수십 개의 웹사이트를 동시에 크롤링할 때, 각각의 요청이 기다리는 동안 CPU를 놀리지 않고 다른 웹사이트로 요청을 보내는 식이다.
이러한 비동기 처리의 중심에는 파이썬의 asyncio
라이브러리가 있으며, 여러 비동기 작업을 효율적으로 스케쥴링해준다.
언제, 어떻게 동시성을 활용해야 할까?
스레드, 프로세스, 비동기 프로그래밍 중 무엇을 언제 활용해야 할까? 몇 가지만 기억하자. 만약 I/O 작업(네트워크, 파일 읽기/쓰기 등)이 많고, CPU 사용량은 적다면 비동기(AsyncIO 기반) 프로그래밍이 최적이다. 사용자 입력과 백그라운드 작업을 동시에 처리해야 하면 스레드를 고려하라. 반면, 이미지 인식, 복잡한 계산, 데이터 변환 등 CPU 점유가 높은 작업이라면 멀티프로세싱이 훨씬 효과적이다.
최신 파이썬에서는 concurrent.futures
모듈이 스레드와 프로세스를 추상화해준다. ThreadPoolExecutor
와 ProcessPoolExecutor
를 통해 상황에 맞는 병렬 처리를 손쉽게 구현할 수 있으니 꼭 익혀두자.
동시성과 병렬성을 적절히 활용할 수 있게 되면, 파이썬으로 만들 수 있는 프로그램의 경계가 대폭 넓어진다. 책의 다음 장에서는 실제 코드와 함께 구체적인 활용법을 더 깊이 다룰 예정이다.