멀티 스레딩은 한 사람이 여러 작업을 동시에 진행하는것과 비슷하다. 사람처럼 멀티 스레딩도 한한 번에 한 가지 일만 할 수 있다. 다만 한 작업이 뭔가를 기다리는 동안 다른 작업으로 전환하므로써 여러 일을 동시에 처리하는 것처럼 보일 뿐이다.
멀티 프로세싱에서는 다수의 계산이 물리적으로 구분되는 다수의 실제 처리 단위(CPU or GPU)들에서 동시에 실행된다. 진정으로 연산이 병렬적으로 실행되는건 멀티프로세싱이라 할 수 있다.
RL을 비롯한 대부분의 deep learning 알고리즘은 계산 속도에 제한을 받으므로 훈련 성능에 도움이 되는것은 멀티 프로세싱을 통한 진정한 동시적 계산이다
파이썬은 병렬 프로그래밍을 위해 multiprocessing 이라는 모듈을 제공한다.
import multiprocessing as mp
if __name__ == "__main__":
proc = mp.current_process()
print(proc.name)
print(proc.pid)
>>MainProcess
>>2596
부모 프로세스가 운영체제에 요청하여 자식 프로세스를 새로 만들어내는 과정
import multiprocessing as mp
def worker():
print("Subprocess End")
if __name__== "__main__":
proc = mp.Process(name="Subprocess", target=worker)
proc.start()
>>Subprocess End
pool은 아주 편리하게 데이터 병렬 처리를 할 수 있게 도와주는 API이다
from multiprocessing import Pool
import numpy as np
def f(x):
return x*x
if __name__ == '__main__':
x = np.arange(25)
p = Pool(5)
print(p.map(f, [x[i*5:i*5+5] for i in range(5)]))
p = Pool(5)
: 다섯개의 프로세스를 가지는 객체를 생성한다.
p.map(target=f, args=[x[i*5:i*5+5] for i in range(5)]))
: p.map
은 p가 가지고 있는 프로세스들에 target function을 할당하고, 인자로 args를 넘긴다.
multiprocessing에서 프로세스는 Process 객체를 생성한 후, start() 메소드를 호출해서 spawn 한다.
from multiprocessing import Process
def f(name):
print(f"hello {name}")
if __name__ == '__main__':
p = Process(target=f, args=('bob',))
p.start()
p.join()
>> hello bob
p = Process(target=f, args=('bob',))
: Process 객체를 생성하고, f 함수를 할당하고 그 인자로 ('bob',)를 넘긴다
p.start()
: start() 메소드를 호출해서 spawn한다.
p.join()
: 프로세스가 완전히 끝나길 기다린다.
자식 프로세스를 생성할때 spawn, fork, forkserver 세가지 방법이 있는데, 우리는 앞에 두가지만 살펴본다.
mp.set_start_method('spawn')
: spawn 방식으로 프로세스를 생성한다.
spawn
부모 프로세스는 깨끗한 새 파이썬 인터프리터 프로세스를 시작한다. 자식 프로세스는 프로세스 객체의 run() 메서드를 실행하는데 필요한 자원만 상속받는다.
mp.set_start_method('fork')
: fork 방식으로 프로세스를 생성한다
fork
부모 프로세스는 os.fork()를 사용하여 자식 프로세스를 포크한다(이더리움 하드포크 이런거 생각하면됨.) 자식 프로세스는 시작될 때 부모 프로세스와 실질적으로 같다.
mp.get_context
로 객체 생성 타입을 바꿀 수 있다.
ctx = mp.get_context('spawn')
a = ctx.Process(target=foo, args=(q,))
multiprocessing에서 Queue와 Pipe는 프로세스 간 통신 채널이 가능하다.
from multiprocessing import Process, Queue
def f(q):
q.put([42, None, 'hello'])
if __name__ == '__main__':
q = Queue()
p = Process(target=f, args=(q,))
p.start()
print(q.get()) # "[42, None, 'hello']" 를 인쇄합니다
p.join()
위 예제에서 q는 Main프로세서 에서 선언된 변수이지만, 자식 프로세스 p에 넘겨주고 다시 받아서 출력까지 할 수 있다.
from multiprocessing import Process, Pipe
def f(conn):
conn.send([42, None, 'hello'])
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=f, args=(child_conn,))
p.start()
print(parent_conn.recv()) # "[42, None, 'hello']" 를 인쇄합니다
p.join()
mp.Pipe()는 한 쌍의 연결된 객체를 생성하고, 위 예제와 같이 .recv()
와 .send()
를 통해 통신한다.
프로세스 간 동기화, 상태,공유