[from getting started to mastering Python] Python threading Local() function usage: return thread local variables

Keywords: Python Java ThreadLocal

Hello, I'm brother Manon Feige. Thank you for reading this article. Welcome to one click three times.
This article focuses on the usage of threading Local() function.
It's full of dry goods. It's recommended to collect it. You need to use it often. If you have any questions and needs, you are welcome to leave a message ~.

preface

When multiple threads access the same common resource, thread safety problems caused by data synchronization may occur if the operation of modifying the common resource is involved. In general, we can deal with this problem by adding mutexes to public resources.

Of course, unless the resources used by multithreading must be set as public resources. If a resource does not need to be shared among multiple threads. We can also use the local() method provided by the Python threading module to avoid thread safety problems.
The local() function of Python threading module is similar to the ThreadLocal class in Java in many ways. Interested partners can take a look at the Java version of threadload Chapter 18: Principle Analysis and application scenario analysis of ThreadLocal.

What is the local() function?

The local() function of threading is mainly used to encapsulate public resources so that the same public resource can be isolated between different threads. How to understand this sentence? For example! Suppose there is a big box (equivalent to public resources), and everyone (equivalent to each thread) puts his mobile phone into the big box. Without any control, when people take out the mobile phone from the big box, they are likely to get it wrong (they can't find the mobile phone they put in). Using the local() function is equivalent to managing this large box. When each person puts in the mobile phone, make a mark (for example, mark the owner's name on the mobile phone) and isolate it into the box. In this way, when people take out the mobile phone from the big box, they can accurately find the mobile phone they put in.

Calling the local() function will generate a ThreadLocal object that can be accessed by all threads, just like the big box in the above example. However, the variables put into the ThreadLocal object are unique to each thread. The variable names are the same, but the values pointed to are completely different.

How to use the local() function?

The basic syntax used by the local() function is:

import threading

local=threading.local()

The first step is to introduce the threading module. The second step is to call the local() function to get the global Threadlocal object. It's always a little dry and tasteless. Then add some salt to the code. Let's start with that big box.

1. No marking and isolation

The first example code is that everyone puts their mobile phones in a large box without marking or isolation. Put it in first and take it out after a period of time.

import threading
import time


def set_telephone(telephone):
    global global_telephone
    global_telephone = telephone
    print(threading.current_thread().name + " The phone you put in is", global_telephone)
    time.sleep(1)
    get_telephone()


def get_telephone():
    print(threading.current_thread().name + " The mobile phone taken out is", global_telephone)


if __name__ == '__main__':
    for i in range(3):
        thread = threading.Thread(target=set_telephone, name='student' + str(i), args=('mobile phone' + str(i),))
        thread.start()

The operation result is:

The mobile phone put in by student 0 is mobile phone 0
 The mobile phone put in by student 1 is mobile phone 1
 The mobile phone student 2 put in is mobile phone 2
 The mobile phone taken out by student 0 is mobile phone 2
 The mobile phone taken out by student 1 is mobile phone 2
 The mobile phone student 2 took out is mobile phone 2

There are three threads that simulate student 0, student 1 and student 2 respectively, and assign various mobile phones to a global variable global_telephone (large box), and then take the global variable global_ Value in telephone. It can be seen that the removed results have become mobile phone 2. This obviously did not meet our expected results. This is the consequence of uncontrolled.

2. Use the local() function to control

If the local() function is used to control, the global variable is replaced with a threadload object, which manages the value in each thread.

import threading
import time


def set_telephone(telephone):
    local.telephone = telephone
    print(threading.current_thread().name + " The phone you put in is", local.telephone + "\n")
    time.sleep(1)
    get_telephone()


def get_telephone():
    print(threading.current_thread().name + " The mobile phone taken out is", local.telephone + "\n")


if __name__ == '__main__':
    local = threading.local()
    for i in range(3):
        thread = threading.Thread(target=set_telephone, name='student' + str(i), args=('mobile phone' + str(i),))
        thread.start()

The operation result is:

The mobile phone put in by student 0 is mobile phone 0

The mobile phone put in by student 1 is mobile phone 1

The mobile phone student 2 put in is mobile phone 2

The mobile phone student 1 took out is mobile phone 1

The mobile phone taken out by student 0 is mobile phone 0

The mobile phone student 2 took out is mobile phone 2

It can be seen that the mobile phone put in by each student is consistent with the mobile phone finally taken out. So how does threading's local() function achieve this effect? We might as well make a reasoning here. It should be a mapping relationship between the mobile phone and its owner. Find your mobile phone according to the owner's unique ID.

3. Simulate the function of local() and create a box

We speculated that we need to define a global dictionary to store the mobile phones put by each student. The key of the dictionary is the thread ID and the value is the specified key value pair. The example code is as follows:

import threading
import time

global_goods_dict = {}

# {
#     "Thread ID":{"telephone": "specific mobile phone put in"},
#     "Thread ID":{"telephone": "specific mobile phone put in"},
#     "Thread ID":{"telephone": "specific mobile phone put in"}
#
# }

def set_telephone(telephone):
    # Get thread ID
    thread_id = threading.get_ident()
    global_goods_dict[thread_id] = {}
    global_goods_dict[thread_id]["telephone"] = telephone
    print(threading.current_thread().name + " The phone you put in is", telephone)
    time.sleep(1)
    get_telephone()


def get_telephone():
    thread_id = threading.get_ident()
    print(threading.current_thread().name + " The mobile phone taken out is", global_goods_dict[thread_id]["telephone"])


if __name__ == '__main__':
    for i in range(3):
        thread = threading.Thread(target=set_telephone, name='student' + str(i), args=('mobile phone' + str(i),))
        thread.start()

The running result is the same as above. Here, a global dictionary global is defined_ goods_ Dict, the keyboard of the dictionary is the thread ID, which ensures that each thread can only get the data set by itself. The value of a dictionary is also a dictionary. This is because a thread may have more than one value to store. Global here_ goods_ dict[thread_ ID] ["telephone"] = telephone is equivalent to local.telephone = telephone in the above example. Although this can achieve results, it is still a little cumbersome to use. So can it be used as smoothly as the local() function.

4. Simplify code operation and further simulate the implementation of local() function

We can set the global_goods_dict dictionary is encapsulated in a class with a class. Let this class set the value automatically

class MyBox:
    box = {}

    def __setattr__(self, key, value):
        thread_id = threading.get_ident()
        # Cell already exists
        if thread_id in MyBox.box:
            MyBox.box[thread_id][key] = value
        else:
            MyBox.box[thread_id] = {key: value}

    def __getattr__(self, item):
        thread_id = threading.get_ident()
        return MyBox.box[thread_id][item]


def set_telephone(telephone):
    myBox.telephone = telephone
    print(threading.current_thread().name + " The phone you put in is", myBox.telephone + "\n")
    time.sleep(1)
    get_telephone()


def get_telephone():
    print(threading.current_thread().name + " The mobile phone taken out is", myBox.telephone + "\n")


if __name__ == '__main__':
    myBox = MyBox()
    for i in range(3):
        thread = threading.Thread(target=set_telephone, name='student' + str(i), args=('mobile phone' + str(i),))
        thread.start()

The operation results are the same as above. Here, a dictionary named box is encapsulated through the MyBox class. The key of the dictionary is the current thread ID, and the value is the key value pair composed of the assigned variable name and value. When executing set_ MyBox. Telephone of telephone method = telephone
, MyBox will actually be called__ setattr__ Method. The parameter key is telephone and the parameter value is "mobile xx". When you call myBox.telephone, you actually call__ getattr__ Method, the passed in parameter item is telephone. Get the current thread ID first when taking value.

summary

Starting from practical examples, this paper introduces the use of local() function of threading module in detail.

Python knowledge map

In order to better help more children learn about Python from the beginning to the end, I got a set of "Python stack knowledge atlas" from CSDN officials, with a size of 870mm x 560mm. After expansion, it can be the size of a desk or folded into the size of a book. Interested children can learn about it - scan the QR code in the figure below to buy it.


I've used it myself. It feels very easy to use. Put the manual on the table and keep knowledge in your heart.

I'm brother Manon Feige. Thank you again for reading this article.

Posted by nielskg on Sat, 04 Sep 2021 22:58:23 -0700