Skip to main content

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 += 1

    are 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