In a multithreaded environment, each thread has its own data. It is better for a thread to use its own local variables rather than global variables, because local variables are only visible to the thread itself and do not affect other threads—whereas modifications to global variables must be protected by locks.
However, local variables also have a drawback: they are cumbersome to pass around during function calls:
def process_student(name):
std = Student(name)
# std is a local variable, but every function needs to use it, so it must be passed in:
do_task_1(std)
do_task_2(std)
def do_task_1(std):
do_subtask_1(std)
do_subtask_2(std)
def do_task_2(std):
do_subtask_2(std)
do_subtask_2(std)
Can you imagine passing parameters like this through every layer of function calls? What about using a global variable? That won’t work either, because each thread processes a different Student object and they cannot be shared.
What if we use a global dict to store all Student objects, and use the thread itself as the key to retrieve the Student object corresponding to the thread?
global_dict = {}
def std_thread(name):
std = Student(name)
# Store std in the global variable global_dict:
global_dict[threading.current_thread()] = std
do_task_1()
do_task_2()
def do_task_1():
# Instead of passing std in, look it up based on the current thread:
std = global_dict[threading.current_thread()]
...
def do_task_2():
# Any function can look up the std variable for the current thread:
std = global_dict[threading.current_thread()]
...
This approach is theoretically feasible. Its biggest advantage is eliminating the problem of passing the std object between layers of functions. However, the code for retrieving std in each function is a bit unsightly.
Is there a simpler way?
ThreadLocal was created for this exact purpose—it eliminates the need to manually look up the dict, as ThreadLocal handles this automatically:
import threading
# Create a global ThreadLocal object:
local_school = threading.local()
def process_student():
# Get the student associated with the current thread:
std = local_school.student
print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
# Bind the student to ThreadLocal:
local_school.student = name
process_student()
t1 = threading.Thread(target=process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()
Execution result:
Hello, Alice (in Thread-A)
Hello, Bob (in Thread-B)
The global variable local_school is a ThreadLocal object. Each thread can read and write the student attribute of this object, but their operations do not interfere with each other. You can think of local_school as a global variable, but each attribute (e.g., local_school.student) is a thread-local variable that can be read and written freely without mutual interference—there is no need to manage locks, as ThreadLocal handles this internally.
You can understand the global local_school as a dict: in addition to local_school.student, you can also bind other variables such as local_school.teacher.
The most common use case for ThreadLocal is binding resources like database connections, HTTP requests, or user identity information to each thread. This allows all processing functions called by a thread to access these resources conveniently.
Although a ThreadLocal variable is a global variable, each thread can only read and write its own independent copy without interfering with other threads. ThreadLocal solves the problem of passing parameters between different functions within a single thread.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import threading
local_school = threading.local()
def process_student():
std = local_school.student
print("Hello, %s (in %s)" % (std, threading.current_thread().name))
def process_thread(name):
local_school.student = name
process_student()
t1 = threading.Thread(target=process_thread, args=("Alice",), name="Thread-A")
t2 = threading.Thread(target=process_thread, args=("Bob",), name="Thread-B")
t1.start()
t2.start()
t1.join()
t2.join()