Python multithreading explanation (how to implement multithreading, recursive locks, mutex locks, semaphores, events, etc.)

What is a thread

Thread, also called lightweight process, is the smallest unit that the operating system can perform operation scheduling. It is included in the process and is the actual operation unit in the process. The thread itself does not own system resources, only a few essential resources in operation, but it can share all the resources owned by the process with other threads belonging to the same process. One thread can create and cancel another thread, and multiple threads in the same process can execute concurrently.

Why use multithreading

Threads are independent and concurrent execution flows in the program. Compared with separate processes, the degree of isolation between threads in a process is smaller, and they share memory, file handles, and the state that other processes should have.
Because the division scale of threads is smaller than that of processes, the concurrency of multithreaded programs is high. The process has an independent memory unit during execution, and multiple threads share memory, which greatly improves the efficiency of the program.
Threads have higher performance than processes. This is because threads in the same process have commonalities. Multiple threads share the virtual space of the same process. The thread shared environment includes process code segments, process public data, etc. By using these shared data, it is easy to communicate between threads.
When the operating system creates a process, it must allocate an independent memory space for the process and allocate a large number of related resources, but it is much simpler to create a thread. Therefore, the performance of using multiple threads to achieve concurrency is much higher than using multiple processes.
To sum up, the use of multi-threaded programming has the following advantages:

  1. Memory cannot be shared between processes, but it is very easy to share memory between threads
  2. When the operating system creates a process, it needs to reallocate system resources for the process, but the cost of creating threads is much smaller. Therefore, it is more efficient to use multiple threads to implement concurrent execution of multiple tasks than to use multiple processes
  3. Python language has built-in multi-threading function support, rather than purely as a scheduling method of the underlying operating system, which simplifies Python's multi-threaded programming

Thread implementation

Ordinary creation of threading module

import threading

def run(n,i):
    print("我是线程", n,i)


if __name__ == '__main__':
    for i in range(10):
        t1 = threading.Thread(target=run, args=("A",i))
        t2 = threading.Thread(target=run, args=("B",i))
        t1.start()
        t2.start()

Method of passing parameters:

Use args to pass parameters threading.Thread(target=fun, args=(10, 100, 100))
Use kwargs to pass parameters threading.Thread(target=fun, kwargs={"a": 10, "b":100, " c”: 100})
Use both args and kwargs to pass parameters threading.Thread(target=fun, args=(10, ), kwargs={“b”: 100,“c”: 100})

Custom thread

import threading

class MyThread(threading.Thread):
    def __init__(self, n):
        super(MyThread, self).__init__()  # 重构run函数必须要写
        self.n = n

    def run(self):
        print("我是线程", self.n)


if __name__ == "__main__":
    for i in range(10):
        t1 = MyThread("t1")
        t2 = MyThread("t2")
        t1.start()
        t2.start()

Daemon thread
Use setDaemon(True) to turn all child threads into daemon threads of the main thread, so when the main process ends, the child threads will also end. So when the main thread ends, the entire program exits.

import threading
import time


def run(n):
    print("task", n)
    time.sleep(1)  # 此时子线程停1s
    print('3')
    time.sleep(1)
    print('2')
    time.sleep(1)
    print('1')


if __name__ == '__main__':
    t = threading.Thread(target=run, args=("t1",))
    t.setDaemon(True)  # 把子进程设置为守护线程,必须在start()之前设置
    t.start()
    print("end")

In the code,
t and print("end") are different threads, and print("end") is the main thread

The main thread waits for the end of the child thread.
In order to let the main thread end after the execution of the daemon thread ends, we can use the join method to let the main thread wait for the execution of the child thread.

import threading
import time

def run(n):
    print("task", n)
    time.sleep(1)       #此时子线程停1s
    print('3')
    time.sleep(1)
    print('2')
    time.sleep(1)
    print('1')

if __name__ == '__main__':
    t = threading.Thread(target=run, args=("t1",))
    t.setDaemon(True)   #把子进程设置为守护线程,必须在start()之前设置
    t.start()
    t.join() # 设置主线程等待子线程结束
    print("end")

Multi-thread sharing of global variables
Thread is the execution unit of the process, and the process is the smallest unit of system allocation of resources, so multiple threads in the same process share resources.

import threading
import time

g_num = 100

def work1():
    global g_num
    for i in range(3):
        g_num += 1
    print("in work1 g_num is : %d" % g_num)

def work2():
    #global g_num
    print("in work2 g_num is : %d" % g_num)

if __name__ == '__main__':
    t1 = threading.Thread(target=work1)
    t1.start()
    time.sleep(1)
    t2 = threading.Thread(target=work2)
    t2.start()


Since mutual exclusion locks are randomly scheduled between threads, and each thread may only execute n executions, dirty data may appear when multiple threads modify the same piece of data at the same time, so thread locks occur, that is, the same time Allows one thread to perform operations. Thread locks are used to lock resources. You can define multiple locks, like the following code. When you need to monopolize a resource, any lock can lock the resource, just like you can use different locks to lock the same one. The door is locked for a reason.

Since threads are randomly scheduled, if multiple threads operate on an object at the same time, if the object is not well protected, the results of the program will be unpredictable. We also call this "thread unsafe".
In order to prevent the above situation from happening, a mutual exclusion lock (Lock) appeared

If you don't use lock

import time, threading

# 假定这是你的银行卡余额:
balance = 0

def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n


def run_thread(n):
    for i in range(1000000):
        change_it(n)

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

A shared variable balance is defined, the initial value is 0, and two threads are started, stored first and then retrieved. Theoretically, the result should be 0. However, because thread scheduling is determined by the operating system, when t1 and t2 are alternately executed , As long as the number of cycles is sufficient, the result of the balance is not necessarily 0.

The reason is because a statement in a high-level language is several statements when the CPU is executed, even a simple calculation:

balance = balance + n

It is also divided into two steps:

x = balance + n
balance = x

If the two statements are executed multiple times, it may not be 0.
The reason is that multiple statements are required to modify the balance. When these statements are executed, the thread may be interrupted, causing multiple threads to change the content of the same object. Messed up.

When two threads deposit and withdraw at the same time, the balance may be wrong. You definitely don't want your bank deposit to become negative inexplicably. Therefore, you must ensure that when one thread modifies the balance, the other thread must not change it.
If we want to ensure that the balance calculation is correct, we must give change_it() a lock. When a thread starts to execute change_it(), we say that because the thread has acquired the lock, other threads cannot execute change_it() at the same time. You can only wait until the lock is released, and then you can change it after acquiring the lock. Since there is only one lock, no matter how many threads, at most only one thread holds the lock at the same time, so there will be no conflict of modification. Creating a lock is achieved by threading.Lock():

import time, threading

# 假定这是你的银行卡余额:
balance = 0

def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n


lock = threading.Lock()
def run_thread(n):
    for i in range(1000000):
        # 先要获取锁:
        lock.acquire()
        try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要释放锁:
            lock.release()


t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)


The usage of the recursive lock RLcok class is exactly the same as the Lock class, but it supports nesting, and the RLcok class is generally used when multiple locks are not released.

import threading
import time

def Func(lock):
    global gl_num
    lock.acquire()
    gl_num += 1
    time.sleep(1)
    print(gl_num)
    lock.release()

if __name__ == '__main__':
    gl_num = 0
    lock = threading.RLock()
    for i in range(10):
        t = threading.Thread(target=Func, args=(lock,))
        t.start()

The semaphore (BoundedSemaphore class)
mutex allows only one thread to change data at the same time, while Semaphore allows a certain number of threads to change data at the same time. For example, if there are 3 pits in the toilet, only 3 people are allowed to go to the toilet, and the people behind can only Wait until someone comes out before you can go in again.

import time

def run(n, semaphore):
    semaphore.acquire()   #加锁
    time.sleep(1)
    print("run the thread:%s\n" % n)
    semaphore.release()     #释放

if __name__ == '__main__':
    num = 0
    semaphore = threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行
    for i in range(22):
        t = threading.Thread(target=run, args=("t-%s" % i, semaphore))
        t.start()
    while threading.active_count() != 1:
        pass  # print threading.active_count()
    else:
        print('-----all threads done-----')

Event (Event class)

The event of the python thread is used for the main thread to control the execution of other threads. The event is a simple thread synchronization object, which mainly provides the following methods:

clear Set the flag to "False"
set Set the flag to "True"
is_set Determine whether the flag is set.
Wait will always monitor the flag. If the flag is not detected, it will always be in a blocked state.
Event processing mechanism: A "Flag" is globally defined , When the flag value is "False", then event.wait() will block, and when the flag value is "True", then event.wait() will no longer block.

#利用Event类模拟红绿灯
import threading
import time

event = threading.Event()


def lighter():
    count = 0
    event.set()     #初始值为绿灯
    while True:
        if 5 < count <=10 :
            event.clear()  # 红灯,清除标志位
            print("\33[41;1mred light is on...\033[0m")
        elif count > 10:
            event.set()  # 绿灯,设置标志位
            count = 0
        else:
            print("\33[42;1mgreen light is on...\033[0m")

        time.sleep(1)
        count += 1

def car(name):
    while True:
        if event.is_set():      #判断是否设置了标志位
            print("[%s] running..."%name)
            time.sleep(1)
        else:
            print("[%s] sees red light,waiting..."%name)
            event.wait()
            print("[%s] green light is on,start going..."%name)

light = threading.Thread(target=lighter,)
light.start()

car = threading.Thread(target=car,args=("MINI",))
car.start()

Insert picture description here
This article mainly refers to the source
https://www.cnblogs.com/luyuze95/p/11289143.html

Guess you like

Origin blog.csdn.net/kobeyu652453/article/details/112778162