Python에서 병렬 처리(parallel processing)는 다중 작업을 동시에 수행하여 효율성을 높이는 방법입니다. 병렬 처리 기법으로는 멀티스레딩, 멀티프로세싱, 비동기 프로그래밍 등이 있습니다. 각 기법은 상황에 따라 다르게 사용될 수 있으며, 이를 적절히 활용하면 성능을 크게 향상시킬 수 있습니다.


Multitasking

Multithreading

Multithreading은 하나의 프로세스 내에서 여러 개의 스레드를 생성하여 동시에 여러 작업을 처리하는 기법입니다. Python에서 멀티스레딩은 threading 모듈을 통해 구현할 수 있습니다. 그러나 Python의 Global Interpreter Lock(GIL) 때문에 멀티스레딩은 CPU 집약적인 작업에서 성능을 크게 향상시키지 않습니다. I/O 작업에서 효과적입니다.

import threading
import time

def print_numbers():
    for i in range(5):
        print(i)
        time.sleep(1)

def print_letters():
    for letter in 'ABCDE':
        print(letter)
        time.sleep(1)

# 스레드 생성
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)

# 스레드 시작
thread1.start()
thread2.start()

# 스레드 종료 대기
thread1.join()
thread2.join()

print("Both threads have finished.")
# 숫자와 문자들이 번갈아가며 출력되고, "Both threads have finished."가 마지막에 출력됨.

이 코드에서는 두 개의 스레드를 동시에 실행하여 숫자와 문자를 번갈아 출력합니다. join() 메서드는 스레드가 모두 종료될 때까지 기다리게 합니다.


Multiprocessing

Multiprocessing은 여러 프로세스를 생성하여 각 프로세스가 독립적으로 실행되도록 하는 기법입니다. 멀티프로세싱은 GIL의 영향을 받지 않기 때문에 CPU 집약적인 작업에서 성능을 극대화할 수 있습니다. Python에서는 multiprocessing 모듈을 사용합니다.

import multiprocessing
import time

def print_square(number):
    print(f"Square of {number}: {number * number}")
    time.sleep(1)

def print_cube(number):
    print(f"Cube of {number}: {number * number * number}")
    time.sleep(1)

if __name__ == '__main__':
    # 멀티프로세싱을 이용한 프로세스 생성
    process1 = multiprocessing.Process(target=print_square, args=(3,))
    process2 = multiprocessing.Process(target=print_cube, args=(3,))

    # 프로세스 시작
    process1.start()
    process2.start()

    # 프로세스 종료 대기
    process1.join()
    process2.join()

    print("Both processes have finished.")
# 3의 제곱과 3의 세제곱이 출력되고, 마지막에 "Both processes have finished."가 출력됨.

위 예시에서는 두 개의 프로세스를 동시에 실행하여 각각 제곱과 세제곱을 계산합니다. 멀티프로세싱을 통해 CPU 집약적인 작업을 병렬로 처리할 수 있습니다.

Global Interpreter Lock

Global Interpreter Lock은 Python에서 멀티스레딩을 사용할 때 발생하는 제한적인 요소입니다. GIL은 한 번에 하나의 스레드만 Python 바이트코드를 실행할 수 있도록 제한합니다. 따라서, Python에서는 멀티스레딩을 사용하더라도 CPU 바운드 작업에서는 성능 향상이 거의 없습니다.

  • CPU 바운드 작업: GIL로 인해 멀티스레딩 성능 향상에 제한이 있어, 멀티프로세싱을 통해 성능 향상 가능
    • 이미지 처리, 대량의 수학 연산, 머신 러닝 연산 등
  • I/O 바운드 작업: GIL의 영향을 덜 받아 멀티스레딩을 통해 성능 향상 가능
    • 웹 크롤링, 파일 입출력, 네트워크 요청 등

Asynchronous

Asynchronous은 하나의 스레드에서 여러 작업을 효율적으로 처리하는 기법입니다. Python에서는 asyncio 모듈을 사용하여 비동기 프로그램을 작성할 수 있습니다. 비동기 프로그래밍은 특히 I/O 작업에서 성능을 향상시키는 데 유리합니다.

import asyncio

async def fetch_data():
    print("Fetching data...")
    await asyncio.sleep(2)
    print("Data fetched!")

async def process_data():
    print("Processing data...")
    await asyncio.sleep(1)
    print("Data processed!")

async def main():
    task1 = asyncio.create_task(fetch_data())
    task2 = asyncio.create_task(process_data())

    await task1
    await task2
    print("Both tasks are finished.")

# 비동기 프로그래밍 실행
asyncio.run(main())
# 
# Fetching data...
# Processing data...
# Data fetched!
# Data processed!
# Both tasks are finished.

이 코드에서는 두 개의 비동기 함수 fetch_dataprocess_data를 동시에 실행하여 I/O 작업을 비동기적으로 처리합니다. await를 사용하여 비동기 작업을 기다리고, asyncio.run()으로 메인 이벤트 루프를 실행합니다.


References