여러개의 작업들이 동시에 일어나는 것
따라서 빨리 끝남
프로그램(하드디스크) -> 윈도우 탐색기, 파일저정소
프로세스(메모리) -> ctrl+alt+del키 눌렀을 때 뜨는 작업관리자, 파일을 더블클릭해서 실행시켰을 때 프로세스가 뜬다(작동한다)고 하는 것, 이때 메모리가 할당됨
from threading import Thread #멀티쓰레드=병렬처리
import time #작업 시간을 체크하기 위한 모듈
def work(work_id, start, end, result): #쓰레드를 하기 위해서는 기본적으로 함수를 정의하고 그 다음에 멀티쓰레드를 정의해야함
total = 0
for i in range(start, end):
total += i
result.append(total)
if __name__ == "__main__": # 현재 실행하는 파일(메인파일,실행파일)일 때만 아래구문을 실행하고 다른 모듈을 불러오는 파일일 때는 실행하지 않는다.
start = time.time() # 코드가 실행되기 전 현재시간
result = []
th1 = Thread(target=work, args=(1, 0, 10000, result)) #target= 내가 적용시키고 싶은 함수, args=(매개변수에 대입되는 인자)
th2 = Thread(target=work, args=(2, 10001, 20000, result))
#th1 = Thread(target=work, args=(1, 0, 1000000, result))
#th2 = Thread(target=work, args=(2, 1000001, 2000000, result)) # 이때 print(time.time()-start)값은 0.15991878509521484
#th1 = Thread(target=work, args=(1, 0, 2000000, result)) # 여긴 0.1575162410736084 멀티쓰레드와 거의 차이가 없음 ㅜㅋㅋ => 강의자료 p195, GIL 정책때문
th1.start() # 위의th1은 단지 선언한 것이고 start()를 해야 비로소 함수가 실행됨
th2.start() # 2가지가 병렬적으로 작업을 함
th1.join() # join () 메소드는 파이썬에게 프로세스가 종료 될 때까지 대기하도록 지시합니다.
# 조인메서드가 없어도 작동하긴 하나 작업을 할때 중간에 프로세스가 종료되어 쓰레드가 일을 하다 말고 꺼져 문제가 생기는 경우를 방지하기 위함
# 불안전한 종료를 방지하기 위한 함수
th2.join()
print(sum(result)) # 199980000 : 0부터 10000-1까지 더한 값과 10001부터 20000-1까지 더한 값의 결과의 합
print(result) # [49995000, 149985000] : [첫번째 쓰레드가 작업한 값, 두번째 쓰레드가 작업한 값]
print(time.time()-start) # 프린트가 실행되는 시점의 시간(time.time()) - 코드가 실행되기 전 시간(start)
# 결국 코드가 실행된 충시간 표시됨
# 위의 코드 메모장에 입력 후 아나콘다 파워쉘 프롬프트에서 실행(python test.py 입력)한 결과값
# 199980000
# [49995000, 149985000]
# test2.py in E:\jupyter notebook
from multiprocessing import Pool
import os
import time
def f(x):
return x*x
print(x, os.getpid()) ##os.getpid: pid는 프로세스 아이디
'''
print(os.getpid())
# 프로세스 아이디 13536, 9692, 8692 이 분배가 됨
13536
9692
8692
8692
9692
8692
13536
9692
'''
print(__name__)
'''
__main__
__mp_main__ #mp: 멀티 프로세스를 의미
__mp_main__ #Pool(4): 풀이 4개이므로 프로세스가 4개가 뜸, 8개로 고치면 8개의 프로세스가 뜸
__mp_main__ #이것의 의미는
__mp_main__
'''
if __name__ == '__main__': # 이 줄이 없으면 오류뜸: main프로세스에서 자식 프로세스 mp_main을 만들고 자식 프로세스가 또 자식 프로세스를 만듬, 무한반복해서 계속 내려감, 에러나는 이유
# if __name__ == '__main__'를 실행하면 자식프로세스는 윗줄은 실행되나 이줄 아래는 실행하지 않음
start = time.time()
p = Pool(4) # 자식 프로세스를 4개를 만들겠다.
result = p.map(f, range(8)) # map함수는 f함수에 collection(여기서는 range(8))을 p를 적당히 분배해서 result에 저장하라는 것
#map함수(※이해인 참고)
# 위의 함수의 return값이 여기 result로 들어감
p.close() # 자식 프로세스를 죽인다.
print(result) # [0, 1, 4, 9, 16, 25, 36, 49]
print(sum(result)) # 140
print(time.time()-start) #range를 10000000(8개)으로 뒀을때 시간체크 (※#print(x, os.getpid()), #print(result)해야 끝없이 나오는 것 방지함)
#pool을 1개 쓸때(4.1068초), 2개 쓸때(2.7816초)와 4개 쓸때(2.36470초) 걸림
# 쓰레드를 2배 더 썼다고 시간이 절반으로 줄어드는 것음 아님
# pool은 자식 프로세스를 띄우는 건데 프로세스는 자원(메모리)과 쓰레드로 구성되어 있어 띄울때마다 메모리 복사도 되어야 한다.
# 새롭게 프로세스가 떴으면 위의 f함수를 다시 불러와야 하고 range의 자원(메모리)도 다시 만들어줘야 한다.
# 프로세스를 띄우는 시간과 함수 작업하는 시간과의 차이가 있기 때문에 시간이 절반이 되지 않는다.
# 프로세스를 띄우는 시간보다 함수실행시간이 더 큰 경우
#ex> 함수의 return이 1) 함수실행시간을 크게: return [x*i for i in range(100000)]이면서
# 2) 프로세스 띄우는 시간을 작게: result = p.map(f, range(100)) 인 경우
#Pool(1) 1.67187초
#Pool(2) 1.05257초
#Pool(4) 0.95512초
#Pool(100) : 무리하게 프로세스를 늘려서 많이 띄우면 오류뜸 ValueError: need at most 63 handles, got a sequence of length 102
#또한 CPU가 돌릴 수 있는 제한이 있기 때문에 프로세스를 많이 띄우면 얘가 효율적으로 자원분배를 못해 속도가 오히려 안나올 수 있다.
#얼마를 늘릴지 그때그때 다르나 많이들 풀의 갯수를 코어의 개수만큼 쓴다.
# test3.py in E:\jupyter notebook
import os
from multiprocessing import Process # 위에서 수행한 Thread와 비슷함
def f(x):
print(os.getpid())
print(x*x)
print(__name__)
'''
__main__
__mp_main__
2392 #프로세스 아이디
1 #리스트0번째 연산값
__mp_main__
14380
4 #리스트1번째 연산값
__mp_main__
12044
16 #리스트4번째 연산값
__mp_main__
14200
9 #리스트3번째 연산값
'''
if __name__ == '__main__':
numbers = [1,2,3,4] #풀은 이 숫자를 마음대로 풀어놓고 프로세스가 이 숫자를 알아서 넣게하는 것이고 프로세스는 정확하게 무엇을 풀지 선언을 해주는 것
#다만 병렬적으로 배정되는 것이기 때문에 순서대로 배정되지 않음
#proc1 = Process(target=f, args=(numbers[0],)) numbers가 지정되지 않았다는 에러뜸
proc1 = Process(target=f, args=(numbers[0],)) #args=(numbers[0],: 콤마를 쓰는 이유는 튜플이기 때문에
proc1.start()
proc2 = Process(target=f, args=(numbers[1],))
proc2.start()
proc3 = Process(target=f, args=(numbers[2],))
proc3.start()
proc4 = Process(target=f, args=(numbers[3],))
proc4.start()
proc1.join()
proc2.join()
proc3.join()
proc4.join()
Pool과 Process 모두 병렬 처리를 위해 사용되지만 차이가 존재한다. 쉽게 설명하자면, Pool은 처리할 일들 을 pool에 뿌려 놓고 알아서 병렬 처리를 하게 만드는 것이고 Process는 각 프로세스별로 할당량을 명시적으 로 적어 두고 그걸 처리하게 하는 것이다.
물론 이는 이해를 돕기 위해 간단하게 설명했을 뿐 실제로 둘의 차이는 조금 더 복잡하다.
정리: 쓰레드는 가볍지만 파이썬의 GIL 정책으로 인해 I/O 처리를 하는 경우에만 주로 효과적이고 프로세스는 각자 가 고유한 메모리 영역을 가지기 때문에 처음 프로세스를 만들 때 시간이 조금 필요하고 더 많은 메모리를 필 요로 하지만 병렬적으로 cpu 작업을 할 수 있어서 빠르다.
import time, os
from multiprocessing import Pool
def func(x):
print("value {} is in Process ID : {}".format(x, os.getpid())) #os.getpid: pid는 프로세스 아이디
return x**5
if __name__ == "__main__":
start = int(time.time())
num_pool = 100 # 2
pool = Pool(num_pool)
print(pool.map(func, range(100)))
print("***run time(sec) :", int(time.time()) - start)
import time, os
from multiprocessing import Pool
def func(x):
return x[0] * x[1]
if __name__ == "__main__":
start = int(time.time())
num_pool = 100 # 2
pool = Pool(num_pool)
print(pool.map(func, zip(range(100),range(100))))
print("***run time(sec) :", int(time.time()) - start)
import time, os
from multiprocessing import Pool
from functools import partial
def func(x, y):
return x * y
if __name__ == "__main__":
start = int(time.time())
num_pool = 100 # 2
pool = Pool(num_pool)
print(pool.map(partial(func,y= 3), range(100)))
print("***run time(sec) :", int(time.time()) - start)