간단하게 설명하면 보통은 다수의 쓰레드가 작동할 때에 아래와 같은 모델을 상상하게 됩니다.
하지만 GIL에 의해서 python은 한 Thread씩 Lock이 걸리는게 아니라 전체에 걸리게 되면서 여러개의 Thread가 Single Processor에서 동작 하는 것 처럼 동작하게 됩니다.
프로그램을 통해 살펴보면
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import threading | |
import time | |
class myThread (threading.Thread): | |
def __init__(self, name): | |
threading.Thread.__init__(self) | |
self.name = name | |
def run(self): | |
print ("Starting " + self.name) | |
startTime = int(time.time()) | |
countdown(100000000) | |
endTime = int(time.time()) | |
print (self.name, "의 작업시간", (endTime-startTime)) | |
print ("Exiting " + self.name) | |
print() | |
def countdown(n): | |
while n > 0: | |
n = n-1; | |
thread1 = myThread("Thread_1") | |
startTime = int(time.time()) | |
thread1.start() | |
thread1.join() | |
print ("Exiting Main Thread") | |
endTime = int(time.time()) | |
print("총 작업 시간", (endTime - startTime)) |
100000000의 숫자를 하나씩 빼면서 while문을 돌리는 간단한 프로그램입니다.
출력 결과는 아래와 같습니다.
---------------------------------------------------
Starting Thread_1
Thread_1 의 작업 시간 8
Exiting Thread_1
Exiting Main Thread
총 작업 시간 8
---------------------------------------------------
또 다른 프로그램을 살펴보면
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import threading | |
import time | |
class myThread (threading.Thread): | |
def __init__(self, name): | |
threading.Thread.__init__(self) | |
self.name = name | |
def run(self): | |
print ("Starting " + self.name) | |
startTime = int(time.time()) | |
countdown(100000000) | |
endTime = int(time.time()) | |
print (self.name, "의 작업 시간", (endTime-startTime)) | |
print ("Exiting " + self.name) | |
print() | |
def countdown(n): | |
while n > 0: | |
n = n-1; | |
thread1 = myThread("Thread_1") | |
thread2 = myThread("Thread_2") | |
startTime = int(time.time()) | |
thread1.start() | |
thread2.start() | |
thread1.join() | |
thread2.join() | |
print ("Exiting Main Thread") | |
endTime = int(time.time()) | |
print("총 작업 시간", (endTime - startTime)) |
기본적으로 생각했을 때엔 위에서 살펴본 하나의 Thread의 작업 시간인 8초와
동일한 시간을 생각 해 볼 수 있습니다. 하지만 결과는 아래와 같습니다.
---------------------------------------------------
Starting Thread_1
Starting Thread_2
Thread_2 의 작업 시간 15
Exiting Thread_2
Thread_1 의 작업 시간 16
Exiting Thread_1
Exiting Main Thread
총 작업 시간 16
---------------------------------------------------
생각했던 것과는 다르게 하나의 Thread의 작업 시간 8초의 두 배인 16초가 출력 되는 것을 보실 수 있습니다.
위와 같은 이유로 python에서는 thread보단 multiprocessing이 권장되어집니다.
1. Multiprocessing
python에서 사용하는 multiprocessing은 fork()와 같게 생각하시면 됩니다.하나 이상의 자식 process를 생성하여 이를 병렬구조로 처리하는 것을 지칭합니다.
import multiprocessing
위와같이 모듈을 불러와 사용 할 수 있습니다.
2. Pool
p = Pool(n)위의 Pool(n)을 통해 미리 process를 생성 할 수 있게 됩니다.
p.map(function, values)
위의 Pool.map을 통해 각 process들이 함수를 동작 시키게 할 수 있습니다.
multiprocessing의 pool을 이용한 예제를 통해 살펴보겠습니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from multiprocessing import Pool | |
import time | |
import os | |
import math | |
def f(x): | |
print("값", x, "에 대한 작업 Pid = ",os.getpid()) | |
time.sleep(1) | |
return x*x | |
if __name__ == '__main__': | |
p = Pool(3) | |
startTime = int(time.time()) | |
print(p.map(f, range(0,10))) | |
endTime = int(time.time()) | |
print("총 작업 시간", (endTime - startTime)) |
프로세서 값 연산시간 누적 연산시간
process 1 -> 0 -------- 1초 -------- 1초
process 2 -> 1 -------- 1초 -------- 1초
process 3 -> 2 -------- 1초 -------- 1초
------------------------------------------------------
process 1 -> 3 -------- 1초 -------- 2초
process 2 -> 4 -------- 1초 -------- 2초
process 3 -> 5 -------- 1초 -------- 2초
------------------------------------------------------
process 1 -> 6 -------- 1초 -------- 3초
process 2 -> 7 -------- 1초 -------- 3초
process 3 -> 8 -------- 1초 -------- 3초
------------------------------------------------------
process 1 -> 9 -------- 1초 -------- 4초
process를 4개로 늘리거나, range를 (0,9)로 수정한다면 누적 연산시간이 3초가 걸리는 것을
확인 하실 수 있습니다.
아래는 출력 결과입니다.
값 0 에 대한 작업 Pid = 3712
값 1 에 대한 작업 Pid = 3740
값 2 에 대한 작업 Pid = 2568
값 3 에 대한 작업 Pid = 3712
값 4 에 대한 작업 Pid = 3740
값 5 에 대한 작업 Pid = 2568
값 6 에 대한 작업 Pid = 3712
값 7 에 대한 작업 Pid = 3740
값 8 에 대한 작업 Pid = 2568
값 9 에 대한 작업 Pid = 3712
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
총 작업 시간 4