Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

파이썬 멀티프로세싱(Multiprocessing) 완벽 가이드: 프로세스 생성부터 통신까지

파이썬의 multiprocessing(멀티프로세싱) 패키지는 새로운 자식 프로세스(child process)를 생성하고 관리할 수 있게 해주는 표준 라이브러리입니다. 이 패키지는 스레딩(threading) 모듈과 유사한 API를 제공하며, 현재 프로세스는 자식 프로세스가 종료되거나 병렬 연산을 계속 수행할 때까지 대기하도록 제어할 수 있습니다.

멀티프로세싱의 기본 구조

멀티프로세싱을 사용할 때는 먼저 Process 객체를 생성하고, 그다음 start() 메서드를 호출하여 프로세스를 시작합니다. 프로세스가 끝날 때까지 기다리려면 join() 메서드를 사용합니다.

기본 예제 코드

from multiprocessing import Process

def display():
    print('Hi !! I am Python')

if __name__ == '__main__':
    p = Process(target=display)
    p.start()
    p.join()

위 예제에서는 먼저 Process 클래스를 임포트한 후, display() 함수를 타깃(target)으로 지정하여 Process 객체를 초기화합니다. 그다음 start() 메서드로 프로세스를 시작하고, join() 메서드로 프로세스가 완료될 때까지 대기합니다.

함수에 인자 전달하기

args 키워드를 사용하면 실행할 함수에 인자를 전달할 수도 있습니다.

from multiprocessing import Process

def display(my_name):
    print('Hi !!!' + " " + my_name)

if __name__ == '__main__':
    p = Process(target=display, args=('Python',))
    p.start()
    p.join()

인자는 반드시 튜플(tuple) 형태로 전달해야 하며, 인자가 하나뿐이라도 위 예제처럼 콤마(,)를 붙여 ('Python',)처럼 작성해야 한다는 점에 유의하세요.

숫자의 세제곱 계산 예제

이번에는 숫자 리스트의 세제곱 값을 계산하여 콘솔에 출력하는 프로세스를 만들어 보겠습니다.

from multiprocessing import Process

def cube(x):
    for x in my_numbers:
        print('%s cube is %s' % (x, x**3))

if __name__ == '__main__':
    my_numbers = [3, 4, 5, 6, 7, 8]
    p = Process(target=cube, args=('x',))
    p.start()
    p.join()
    print("Done")

출력 결과

Done
3 cube is 27
4 cube is 64
5 cube is 125
6 cube is 216
7 cube is 343
8 cube is 512

여러 프로세스 동시 실행하기

멀티프로세싱은 한 번에 두 개 이상의 프로세스를 동시에 실행하는 것도 가능합니다.

아래 예제에서는 첫 번째 프로세스(process1)가 숫자의 세제곱을 계산하는 동안, 동시에 두 번째 프로세스(process2)가 해당 숫자가 짝수인지 홀수인지 판별합니다.

from multiprocessing import Process

def cube(x):
    for x in my_numbers:
        print('%s cube is %s' % (x, x**3))

def evenno(x):
    for x in my_numbers:
        if x % 2 == 0:
            print('%s is an even number ' % (x))

if __name__ == '__main__':
    my_numbers = [3, 4, 5, 6, 7, 8]
    my_process1 = Process(target=cube, args=('x',))
    my_process2 = Process(target=evenno, args=('x',))
    my_process1.start()
    my_process2.start()
    my_process1.join()
    my_process2.join()
    print("Done")

출력 결과

3 cube is 27
4 cube is 64
5 cube is 125
6 cube is 216
7 cube is 343
8 cube is 512
4 is an even number
6 is an even number
8 is an even number
Done

프로세스 간 통신 (Inter-Process Communication)

멀티프로세싱은 프로세스 사이에서 데이터를 주고받을 수 있는 두 가지 통신 채널, 즉 Pipe(파이프)Queue(큐)를 지원합니다.

Pipe (파이프)

프로세스 간에 직접 데이터를 주고받아야 할 때는 Pipe를 사용합니다.

from multiprocessing import Process, Pipe

def myfunction(conn):
    conn.send(['hi!! I am Python'])
    conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=myfunction, args=(child_conn,))
    p.start()
    print(parent_conn.recv())
    p.join()

출력 결과

['hi !!! I am Python']

Pipe는 두 개의 연결(connection) 객체를 반환하며, 이 객체들은 파이프의 양쪽 끝단을 나타냅니다. 각 연결 객체는 데이터를 전송하는 send() 메서드와 데이터를 수신하는 recv() 메서드를 제공합니다.

위 예제에서는 먼저 하나의 프로세스를 생성하고, 이 프로세스가 "hi!! I am Python" 메시지를 출력한 뒤 데이터를 다른 프로세스로 전달합니다.

Queue (큐)

프로세스 사이에 데이터를 전달할 때는 Queue 객체를 사용할 수도 있습니다.

import multiprocessing

def evenno(numbers, q):
    for n in numbers:
        if n % 2 == 0:
            q.put(n)

if __name__ == "__main__":
    q = multiprocessing.Queue()
    p = multiprocessing.Process(target=evenno, args=(range(10), q))
    p.start()
    p.join()
    while q:
        print(q.get())

출력 결과

0
2
4
6
8

위 예제에서는 먼저 숫자가 짝수인지 판별하는 함수를 정의합니다. 숫자가 짝수이면 큐의 맨 뒤에 삽입(put)됩니다. 그다음 큐 객체와 프로세스 객체를 생성하고 프로세스를 시작한 뒤, 마지막으로 큐가 비어 있는지 확인하며 데이터를 꺼냅니다.

숫자를 출력할 때는 큐 맨 앞에 있는 값부터 차례대로 하나씩 출력되며, FIFO(선입선출) 방식으로 동작합니다.

Lock (락) — 동시 접근 제어

여러 프로세스가 동시에 같은 코드 영역에 접근하는 것을 막고 싶을 때는 Lock을 사용합니다. 락이 걸려 있는 동안에는 다른 프로세스가 유사한 코드를 실행할 수 없으며, 프로세스가 작업을 완료하면 락이 해제됩니다.

Lock 사용 예제

from multiprocessing import Process, Lock

def display_name(l, i):
    l.acquire()
    print('Hi', i)
    l.release()

if __name__ == '__main__':
    my_lock = Lock()
    my_name = ['Aadrika', 'Adwaita', 'Sakya', 'Sanj']
    for name in my_name:
        Process(target=display_name, args=(my_lock, name)).start()

출력 결과

Hi Aadrika
Hi Adwaita
Hi Sakya
Hi Sanj

acquire()로 락을 획득한 프로세스만 공유 자원에 접근할 수 있고, 작업이 끝나면 반드시 release()로 락을 반환해야 다른 프로세스가 진행할 수 있습니다.

Logging (로깅)

multiprocessing 모듈은 로깅(logging) 기능도 함께 제공합니다. 일반 logging 패키지는 내부적으로 락(lock)을 사용하지 않기 때문에, 여러 프로세스가 동시에 로그를 남기면 실행 중에 메시지가 서로 섞일 수 있습니다. 멀티프로세싱 환경에서는 이 문제를 피하기 위해 전용 로깅 유틸리티를 사용하는 것이 좋습니다.

import multiprocessing, logging

logger = multiprocessing.log_to_stderr()
logger.setLevel(logging.INFO)
logger.warning('Error has occurred')

위 예제에서는 먼저 logging과 multiprocessing 모듈을 임포트한 후, multiprocessing.log_to_stderr() 메서드를 호출합니다. 이 메서드는 내부적으로 get_logger()를 호출하여 로거를 가져오고, sys.stderr에 로그 출력을 추가합니다. 마지막으로 로거의 레벨을 설정하고 메시지를 전달하면 됩니다.