나는 생각했다.
그러면 GIL 때문에 쓰레드 작업에서 락걸거나, 따로 통제할 필요가 없겠네.
GIL 특성상 무조건 딱 하나의 스레드만 실행시켜주니깐!
하지만 이 말은 틀렸다.
GIL는 동시접근을 보장해주는 락역활이라고 생각했지만 그렇지않다.
그 이유는 코드로 확인해보자.
class Counter:
def __init__(self):
self.count = 0
def increment(self, offset):
self.count += offset
def worker(sensor_index, how_many, counter):
BARRIER.wait()
for _ in range(how_many):
counter.increment(1)
# Example 3
from threading import Barrier
BARRIER = Barrier(5)
from threading import Thread
how_many = 10**5
counter = Counter()
threads = []
for i in range(5):
thread = Thread(target=worker,
args=(i, how_many, counter))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
expected = how_many * 5
found = counter.count
print(f'Counter should be {expected}, got {found}')
위 코드를 쭉 읽어보면 스레드에 연산할 수 있는 갯수까지 정의해주었다.
그러면 GIL이 당연히 락역활을 해주니깐 순차적으로 진행해서 정답은 500000(10**5 * 5) 이겠네? 라고 생각할 거고,
이론적으로도 그게 맞다. 하지만 정답은 예상할 수 없다이다.
고개가 갸우뚱.. 이 문제를 이해하기 위해선 스레드 감수성이 필요하다
스레드는 이 코드를 만나고
self.count += offset
를 통해 count를 증감시키고 있었다. 하지만 위 코드의 연산은 실제로 이렇게 작동된다.
value = getattr(counter, 'count')
result = value + 1
setattr(counter, 'count', result)
나는 분명 self.count += offset에 오면 종료할 수 있게 설계되었다구!
그래서 종료를 했는데 하필이면 result = value+1 까지 왔을 때, 스레드가 스위칭이 되었다.
즉, 스레드의 컨디션이 좋으면 전부 실행되지만, 컨디션이 안좋다면 전부 실행하지 않게 될 수 있는 것이다.
이를 보완하기 위해 Lock 클래스를 사용하여 상호배제 락을 확실히 이행할 수 있다.
from threading import Lock
class LockingCounter:
def __init__(self):
self.lock = Lock()
self.count = 0
def increment(self, offset):
with self.lock:
self.count += offset
BARRIER = Barrier(5)
counter = LockingCounter()
for i in range(5):
thread = Thread(target=worker,
args=(i, how_many, counter))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
expected = how_many * 5
found = counter.count
print(f'Counter should be {expected}, got {found}')
'프로그래밍 언어 > Python' 카테고리의 다른 글
Thread와 Queue를 활용한 파이프라인 구현하기( feat. 콘웨이 생명게임 ) (0) | 2023.09.19 |
---|---|
파이썬에서 외부 터미널 명령어 사용하기 ( subprocess ) (0) | 2023.09.19 |
두 코드의 차이점을 설명하시요( feat. Python,쓰레드, GIL, 병렬 I/O) (0) | 2023.09.15 |
클래스를 디버깅해보자(feat.메타클래스,클래스 데코레이터) (0) | 2023.09.15 |
디스크립터 __set_name__ 의 활용방법 (0) | 2023.09.14 |