Appearance
ThreadLocal
In a multithreaded environment, each thread has its own data. Using local variables specific to a thread is better than using global variables because local variables are only visible to the thread itself and do not affect other threads. Modifying global variables requires locks.
However, local variables also present a challenge when passing them around during function calls:
python
def process_student(name):
std = Student(name)
# std is a local variable, but each 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_1(std)
do_subtask_2(std)
If every function in the call chain has to pass parameters like this, it can become unwieldy. Using global variables isn't a solution either, since each thread handles different Student
objects, and they cannot be shared.
One possible solution is to use a global dictionary to store all the Student
objects, with the thread itself as the key to retrieve the corresponding Student
object:
python
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():
# Retrieve std 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()]
...
Theoretically, this approach eliminates the need to pass the std
object through each layer of function calls. However, the code to retrieve std
in each function can be cumbersome.
Is there a simpler way?
This is where ThreadLocal
comes into play. It eliminates the need to look up a dictionary, as ThreadLocal
automatically handles this:
python
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 the 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()
The output will be:
Hello, Alice (in Thread-A)
Hello, Bob (in Thread-B)
The global variable local_school
is a ThreadLocal
object, allowing each thread to read and write to the student
attribute without interfering with each other. You can think of local_school
as a global variable, but each attribute, such as local_school.student
, acts as a local variable for the thread, allowing for any read and write without the need to manage locks; ThreadLocal
handles that internally.
You can view local_school
as a dictionary that can not only use local_school.student
but can also bind other variables, such as local_school.teacher
, etc.
The most common use of ThreadLocal
is to bind a database connection, HTTP request, user identity information, etc., for each thread. This way, all the functions called by a thread can easily access these resources.
Summary
A ThreadLocal
variable, while a global variable, allows each thread to read and write to its own independent copy without interference. ThreadLocal
solves the problem of passing parameters between functions within a single thread.