Skip to content
On this page

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.

ThreadLocal has loaded