Appendix-06.Thread_and_scope
Threads share same global scope
import threading
import time
count = 0
def try_count(sleep_seconds, name):
global count
count += 1
time.sleep(sleep_seconds)
# print(f"\nIn thread: {threading.current_thread().name}: count = {count}")
print(f"\nIn thread: {name}: count = {count}")
t1 = threading.Thread(target=try_count, args=(1, "T1"))
t2 = threading.Thread(target=try_count, args=(2, "T2"))
t3 = threading.Thread(target=try_count, args=(1, "T3"))
# unpredicatble
t1.start()
t2.start()
t3.start()
In thread: T1: count = 3 In thread: T3: count = 3
In thread: T2: count = 3
thread-safe
-
The above sample code is not thread safe
-
Even though Python has a GIL, operations like:
count += 1are not atomic. They expand into:
-
load count
-
add 1
-
store count
Two threads can interleave and cause race conditions.
-
-
We can use lock to solve race condition
lock = threading.Lock()
def try_count(sleep_seconds, name):
global count
with lock:
count += 1
time.sleep(sleep_seconds)
print(f"\nIn thread: {name}: count = {count}")
thread.local
- If you want per-thread state instead of shared state using
threading.local()
import threading
import time
thread_local = threading.local()
def try_count(sleep_seconds, name):
thread_local.count = getattr(thread_local, "count", 0)
thread_local.count += 1
time.sleep(sleep_seconds)
print(f"\nIn thread: {name}: count = {thread_local.count}")
t1 = threading.Thread(target=try_count, args=(1, "T1"))
t2 = threading.Thread(target=try_count, args=(2, "T2"))
t3 = threading.Thread(target=try_count, args=(1, "T3"))
# unpredicatble
t1.start()
t2.start()
t3.start()
In thread: T1: count = 1 In thread: T3: count = 1
In thread: T2: count = 1