파이썬에서 멀티프로세싱을 이용하여 여러 작업을 동시에 처리할 수 있다. multiprocessing의 Process를 사용하여 이를 간단히 구현할 수 있다.
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도 출력되지 않았다.
'Study > Python' 카테고리의 다른 글
Pytorch의 Reproducibility를 위한 설정들 (0) | 2021.06.09 |
---|---|
Python multiprocessing.Pool 멀티프로세싱 2 (1) | 2021.06.07 |
Python collections의 Counter로 개수 세기 (0) | 2021.05.27 |
python에서 json 다루기 (0) | 2021.05.24 |
python의 with 구문 (context manager) 한번 보기 (0) | 2021.05.19 |
댓글