기본 콘텐츠로 건너뛰기

Python에서 멀티프로세싱을 사용하는 실전 예제

개요

Python에서 멀티프로세싱을 활용하면 CPU 바운드 작업의 처리 속도를 획기적으로 향상시킬 수 있습니다. 단일 코어만 사용하는 싱글 스레드 방식과 달리, 멀티프로세싱은 여러 코어를 활용하여 병렬 처리를 가능하게 하므로, 특히 CPU 사용률이 높은 작업에 효과적입니다. 이 포스트에서는 실제 코드 예제를 통해 Python의 multiprocessing 모듈을 사용하는 방법과 주의사항을 알아보겠습니다.

핵심 개념 정리

Python의 multiprocessing 모듈은 프로세스 생성 및 관리를 위한 다양한 기능을 제공합니다. 핵심 클래스는 Process이며, 이를 이용해 새로운 프로세스를 생성하고, target 인자로 실행할 함수를 지정합니다. argskwargs 인자를 통해 함수에 인자를 전달할 수 있습니다. Pool 클래스는 여러 프로세스를 효율적으로 관리하는 데 유용하며, map, apply_async 등의 메서드를 통해 병렬 처리 작업을 수행할 수 있습니다.

실전 코드 예제

다음은 CPU 바운드 작업인 소수 판별을 멀티프로세싱으로 구현한 예제입니다. is_prime 함수가 소수 판별 로직을 담고 있으며, multiprocessing.Pool을 사용하여 여러 프로세스에서 병렬적으로 실행합니다.

import multiprocessing
import time

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            return False
    return True

if __name__ == '__main__':
    numbers = list(range(100000)) # 판별할 숫자 리스트
    start_time = time.time()

    # 싱글 스레드 방식
    single_result = list(map(is_prime, numbers))
    single_time = time.time() - start_time
    print(f"Single-threaded time: {single_time:.4f} seconds")


    start_time = time.time()
    with multiprocessing.Pool(processes=multiprocessing.cpu_count()) as pool:
        multi_result = pool.map(is_prime, numbers)
    multi_time = time.time() - start_time
    print(f"Multi-processed time: {multi_time:.4f} seconds")

    # 결과 확인 (생략 가능)
    #assert single_result == multi_result

코드 실행 결과, 멀티프로세싱 방식이 싱글 스레드 방식보다 훨씬 빠르게 처리되는 것을 확인할 수 있습니다. multiprocessing.cpu_count()는 시스템의 CPU 코어 수를 자동으로 감지하여 적절한 프로세스 수를 설정합니다.

주의사항 또는 실무 팁

  • GIL (Global Interpreter Lock): Python의 GIL은 한 번에 하나의 스레드만 Python 바이트코드를 실행하도록 제한합니다. 따라서 I/O 바운드 작업에는 멀티스레딩이 효과적일 수 있지만, CPU 바운드 작업에는 멀티프로세싱이 훨씬 효율적입니다.
  • 프로세스 생성 오버헤드: 프로세스 생성에는 시간이 소요됩니다. 작업의 크기가 작다면 오버헤드가 처리 시간보다 클 수 있으므로, 멀티프로세싱이 오히려 느릴 수 있습니다. 작업 크기와 프로세스 수를 적절히 조절해야 합니다.
  • 공유 메모리: 프로세스 간 데이터 공유는 주의가 필요합니다. multiprocessing.Value, multiprocessing.Array, multiprocessing.Manager 등을 사용하여 안전하게 데이터를 공유할 수 있습니다.
  • 에러 핸들링: 예외 처리를 철저히 하여 프로세스가 비정상 종료되는 것을 방지해야 합니다.

결론 또는 마무리 조언

Python의 멀티프로세싱은 CPU 바운드 작업의 성능을 크게 향상시키는 강력한 도구입니다. 하지만 GIL과 프로세스 생성 오버헤드 등을 고려하여 작업의 특성에 맞게 적절히 활용해야 합니다. 이 포스트에서 소개한 예제 코드와 주의사항을 참고하여 여러분의 프로그램 성능을 개선해 보세요.

댓글

이 블로그의 인기 게시물

Django에서 트랜잭션 관리하기

Django에서 트랜잭션 관리하기 안녕하세요! 오늘은 Django에서 데이터베이스 트랜잭션을 효과적으로 관리하는 방법에 대해 알아보겠습니다. 1. 트랜잭션의 중요성 트랜잭션은 데이터베이스의 일관성과 무결성을 보장하는 중요한 개념입니다. Django에서는 여러 가지 방법으로 트랜잭션을 관리할 수 있습니다. 1.1 기본 개념 원자성(Atomicity) : 트랜잭션은 모두 실행되거나 모두 실행되지 않아야 합니다. 일관성(Consistency) : 트랜잭션 전후로 데이터베이스의 일관성이 유지되어야 합니다. 격리성(Isolation) : 동시에 실행되는 트랜잭션들이 서로 영향을 주지 않아야 합니다. 지속성(Durability) : 완료된 트랜잭션의 결과는 영구적으로 저장되어야 합니다. 2. Django의 트랜잭션 관리 2.1 기본 설정 # settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'mydatabase', 'USER': 'myuser', 'PASSWORD': 'mypassword', 'HOST': 'localhost', 'PORT': '5432', 'ATOMIC_REQUESTS': True, # 모든 뷰를 트랜잭션으로 래핑 } } 2.2 데코레이터 사용 from django.db import transaction @transaction.atomic def create_order(user, items): order = Order.objects.create(user=...

AWS S3 + CloudFront로 정적 파일 서빙 완전 가이드

AWS S3 + CloudFront로 정적 파일 서빙 완전 가이드 안녕하세요! 오늘은 AWS S3와 CloudFront를 사용하여 정적 파일을 효율적으로 서빙하는 방법에 대해 알아보겠습니다. 왜 S3와 CloudFront를 사용할까요? 높은 가용성 : AWS의 글로벌 인프라를 활용 빠른 전송 속도 : CloudFront의 CDN 기능으로 전 세계 사용자에게 빠른 전송 비용 효율성 : 사용한 만큼만 지불 보안 : AWS의 보안 기능 활용 확장성 : 트래픽 증가에 자동 대응 1. S3 버킷 설정 1.1 버킷 생성 및 설정 import boto3 def create_s3_bucket(): s3 = boto3.client('s3') # 버킷 생성 bucket_name = 'your-static-files-bucket' s3.create_bucket( Bucket=bucket_name, CreateBucketConfiguration={ 'LocationConstraint': 'ap-northeast-2' } ) # 버킷 정책 설정 bucket_policy = { "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObje...

Python에서 asyncio 완전 정복 (await, async, gather 등)

어휴, 요즘 파이썬으로 비동기 프로그래밍 하는 재미에 푹 빠졌어요! 특히 asyncio 는 정말 마법 같더라고요. 처음엔 좀 낯설었는데, 익숙해지니까 속도 향상이 눈에 띄게 느껴져서 완전 반해버렸습니다. 이 글에선 제가 asyncio 를 배우면서 깨달은 점들을 풀어놓을게요. 혹시 비동기 프로그래밍이 뭔지 잘 모르시겠다면, 간단히 말해 여러 작업을 동시에 처리해서 프로그램 속도를 엄청나게 높이는 기술이라고 생각하시면 돼요. 마치 여러 요리사가 동시에 음식을 만들어서 손님에게 빨리 제공하는 것과 비슷하죠! 일단 async 와 await 라는 녀석들이 핵심인데요, async 는 함수 앞에 붙여서 "얘는 비동기 함수야!"라고 선언하는 거예요. 그리고 await 는 다른 비동기 함수가 끝날 때까지 기다리라고 지시하는 역할을 하죠. 예를 들어, 네트워크에서 데이터를 가져오는 함수가 있다면, await 를 사용해서 데이터가 다 가져올 때까지 기다렸다가 다음 작업을 진행할 수 있어요. 그 동안 다른 작업을 처리할 수 있으니, 마치 멀티태스킹을 하는 것처럼 느껴져요. 신기하지 않나요? 그리고 asyncio.gather 는 여러 비동기 함수를 동시에 실행하고 결과를 모아주는 아주 유용한 친구입니다. 제가 웹사이트 여러 개에서 데이터를 동시에 가져와야 할 때 정말 요긴하게 썼어요. 하나씩 순서대로 가져오는 것보다 훨씬 빠르더라고요! 마치 여러 개의 탭을 동시에 열어놓고 작업하는 것과 같다고 생각하시면 될 것 같아요. 실제로 제가 썼던 코드를 보여드릴게요. 세 개의 웹사이트에서 데이터를 가져오는 예제인데요. (아래 코드 삽입) 이 코드를 보시면, fetch_data 함수가 각 웹사이트에서 데이터를 가져오는 역할을 하고, asyncio.gather 가 이 함수들을 동시에 실행하도록 도와주는 것을 볼 수 있을 거예요. asyncio.sleep(2) 는 네트워크 지연을 시뮬레이션하기 위해 넣...