본문 바로가기
Study/Python

Python multiprocessing.Process 멀티프로세싱 1

by 개발새-발 2021. 6. 3.
반응형

파이썬에서 멀티프로세싱을 이용하여 여러 작업을 동시에 처리할 수 있다. multiprocessingProcess를 사용하여 이를 간단히 구현할 수 있다.

Process 사용

아래 코드는 Process를 사용하는 가장 간단한 방법이다.

from multiprocessing import Process

def func(string):
    print(string)

if __name__ == "__main__":
    proc = Process(target=func,args=('process',))
    proc.start()
process

Process의 서브클래스를 만들어 사용할 수도 있다. 이때, 반드시 run() 메서드를 오버 라이딩해주자.

from multiprocessing import Process

class MyProcess(Process):

    def __init__(self,string):
        Process.__init__(self)
        self.string = string

    def run(self):
        print(self.string)

if __name__ == "__main__":
    proc = MyProcess('process')
    proc.start()
process

 

실행시간 비교

파이썬의 Process를 이용하여 전체 프로그램의 실행시간을 줄일 수 있는지 확인해보자. 한번 실행하는 데에 약 2초가 소요되는 func이 있다. 이 메서드를 Process를 사용하지 않고 두 번 호출한 결과와 Process를 사용하여 2번 호출한 경우를 비교해 보자.

from multiprocessing import Process
import time

def func(name):
    start = time.time()
    print(name, " started")
    time.sleep(2)
    delta_t = time.time()-start
    print(name, " ended, took ",delta_t)

if __name__ == "__main__":
    # Without Process
    start = time.time()
    func("No Process 1")
    func("No Process 2")
    delta_t = time.time() - start
    print("Without Process : ",delta_t,"s")
    
    # With Process
    start = time.time()
    proc1 = Process(target=func,args=('process1',))
    proc2 = Process(target=func,args=('process2',))
    proc1.start()
    proc2.start()
    proc1.join()
    proc2.join()
    delta_t = time.time()- start
    print("With Process : ",delta_t,"s")
No Process 1  started
No Process 1  ended, took  2.0005617141723633
No Process 2  started
No Process 2  ended, took  2.008075475692749
Without Process :  4.008637189865112 s
process1  started
process2  started
process2  ended, took  2.0015156269073486
process1  ended, took  2.0015156269073486
With Process :  2.0763368606567383 s

Process를 사용하지 않은 경우 순차적으로 진행되기 때문에 처음 func이 끝나기 전에는 다음 줄의 func이 실행되지 않는다. 그러나 Process를 사용한 경우 func을 두 개의 프로세스에서 동시에 실행하여 func을 한번 실행한 것과 시간 차이가 크지 않았다. 위 실행결과를 보면 Process를 사용한 경우에 proc1의 실행이 끝나기 전임에도 proc2가 시작된 것을 볼 수 있다.

 

서브 프로세스 기다리기 join()

join을 사용하여 메인 프로세스에서 서브 프로세스를 기다리게 할 수 있다. 반대로 말하면, join을 사용하지 않을 때 메인 프로세스는 서브 프로세스 시작 이후 서브 프로세스와 관계없이 남은 코드들을 실행한다. join()을 포함한 코드와 그렇지 않은 코드를 비교해보자.

from multiprocessing import Process
import time

def func(string):
    time.sleep(5)
    print(string," Ended")

if __name__ == "__main__":
    # Code without join()
    proc = Process(target=func,args=('process',))
    proc.start()
    
    # do some stuffs
    a = 0
    for i in range(10):
        a+=i
    print(a)
    time.sleep(2)

    print("Last line of main!!!")
45
Last line of main!!!
process  Ended 
from multiprocessing import Process
import time

def func(string):
    time.sleep(5)
    print(string," Ended")

if __name__ == "__main__":
    # Code with join()
    proc = Process(target=func,args=('process',))
    proc.start()
    
    # do some stuffs
    a = 0
    for i in range(10):
        a+=i
    print(a)
    time.sleep(2)

    # Join HERE!
    proc.join()
    print("Last line of main!!!")
45
process  Ended
Last line of main!!!

join을 사용하지 않은 코드에서는 서브 프로세스를 실행하고 난 후 메인 프로세스는 자신의 코드를 모두 실행하였다.  그러나 join을 사용한 코드에서는 join을 사용한 위치에서 join을 사용해준 Process가 끝나기를 기다려주는 것을 볼 수 있다. 단, join에 숫자 값을 넣어주면 주어진 시간만큼만 기다렸다가 이후 코드를 계속 실행한다.

 

Daemon

위의 코드들을 실행해보면, 메인 프로세스에서 마지막 줄의 코드까지 다 실행하였음에도 서브 프로세스가 종료되지 않았다면 같이 종료되지 않는 모습을 보인다. 그러나 Daemon 프로세스로 만들어주면 메인 프로세스가 종료하려 할 때 이를 기다려주지 않아 프로그램이 종료되는 것을 볼 수 있다. daemon으로 설정해주기 위해서는 그저 Process생성 시 daemon값을 True로 넣어주면 된다. 기본값은 False이다. 아래 daemon을 설정한 코드와 그렇지 않은 코드를 비교해보자.

from multiprocessing import Process
import time

def func(string):
    print(string,' started')
    time.sleep(5)
    print(string,' ended')

if __name__ =='__main__':
    # Code without daemon
    proc = Process(target=func,args=("subprocess",))
    proc.start()

    time.sleep(1)

    print('This is last line of code!')
subprocess  started
This is last line of code!
subprocess  ended
from multiprocessing import Process
import time

def func(string):
    print(string,' started')
    time.sleep(5)
    print(string,' ended')

if __name__ =='__main__':
    # Code with daemon
    proc = Process(target=func,args=("subprocess",),daemon=True)
    proc.start()

    time.sleep(1)

    print('This is last line of code!')
subprocess  started
This is last line of code!

서브 프로세스를 데몬으로 만들었을 때 서브 프로세스의 작업이 완전히 끝나지 않았음에도 프로그램이 종료된 것을 볼 수 있다. (python multiprocessing의 daemon은 unix에서 말하는 daemon과 차이가 있는 것 같다.)

 

프로세스 식별 pid, name

Pid로 프로세스를 식별할 수 있다. Process에는 pid값이 있다. multiprocessing.current_process()로 현재 프로세스를 불러와 프로세스의 pid값을 읽으면 된다. os.getpid()를 사용하는 방법도 있다. 부모 프로세스의 pid도 알아낼 수 있다. multiprocessing.parent_process()로 부모 프로세스를 불러와 pid를 읽으면 된다. os.getppid()를 사용하여도 된다.

import multiprocessing as mp
from multiprocessing import Process
import os
import time

def func1():
    c_process = mp.current_process()
    p_process = mp.parent_process()
    print('Started. , pid : ',c_process.pid,' ppid : ',p_process.pid)
    time.sleep(1)
    print('Ended. , pid : ',c_process.pid,' ppid : ',p_process.pid)

def func2():
    print('Started. , pid : ',os.getpid(),' ppid : ',os.getppid())
    time.sleep(1)
    print('Ended. , pid : ',os.getpid(),' ppid : ',os.getppid())


if __name__ =='__main__':
    proc1 = Process(target=func1)
    proc2 = Process(target=func2)
    proc1.start()
    proc2.start()
Started. , pid :  10932  ppid :  1148
Started. , pid :  15792  ppid :  1148
Ended. , pid :  10932  ppid :  1148
Ended. , pid :  15792  ppid :  1148

 

프로세스를 식별하는 데에 pid값이 아닌 name으로 구별할 수 도 있다.  name값은 Process생성 시 지정해 줄 수 있으며, 지정해주지 않은 경우 "Process-숫자" 형태로 자동으로 name이 부여된다.

import multiprocessing as mp
from multiprocessing import Process
import os
import time

def func():
    c_process = mp.current_process()
    print('Started. , name : ',c_process.name,'pid : ',c_process.pid)
    time.sleep(1)
    print('Ended. , name : ',c_process.name,'pid : ',c_process.pid)


if __name__ =='__main__':
    proc1 = Process(target=func,name = 'MyProcess')
    proc2 = Process(target=func)
    proc1.start()
    proc2.start()
Started. , name :  Process-2 pid :  14200
Started. , name :  MyProcess pid :  4804
Ended. , name :  Process-2 pid :  14200
Ended. , name :  MyProcess pid :  4804

 

프로세스 생존 여부 is_alive()

생성된 프로세스가 아직 실행 중인지, 아닌지 여부를 is_alive()로 판단할 수 있다.

from multiprocessing import Process
import os
import time

def func():
    print('Started.') 
    time.sleep(3)
    print('Ended.')


if __name__ =='__main__':
    proc = Process(target=func,name = 'MyProcess')
    proc.start()
    time.sleep(1)
    
    # alive here
    print(proc.is_alive())
    time.sleep(3)
    
    # already dead here
    print(proc.is_alive())
Started.
True
Ended.
False

 

강제 종료 terminate(), kill()

생성한 프로세스를 끝낼 수 있다. 둘의 차이는 Unix에서 terminate()는 SIGTERM, kill()은 SIGKILL 신호를 보낸다는 것이다. 프로세스 종료까지 약간의 시간이 필요할 수 있음으로 필요한 경우 이후에 join()을 사용하여 흐름을 컨트롤하는 것이 좋다.

from multiprocessing import Process
import os
import time

def func():
    print('Started.') 
    time.sleep(100)
    print('Ended.')

if __name__ =='__main__':
    proc = Process(target=func,name = 'MyProcess')
    proc.start()
    time.sleep(1)

    # alive here
    print(proc.is_alive())
    
    # terminate
    proc.terminate()
    print(proc.is_alive())

    # wait till proc dead
    proc.join()
    print(proc.is_alive())
Started.
True
True
False

proc가 정상적으로 종료되었다면 100초 후 Ended를 출력하며 프로그램이 종료되었을 것이다. 그러나 강제 종료되었기 때문에 위 프로그램의 실행시간은 2초도 걸리지 않으며, Ended도 출력되지 않았다.

반응형

댓글