Python threading multi thread advanced (thread guard, thread synchronization - mutex and recursive lock, BoundedSemaphore, Event)

Keywords: Python

Article catalog

As for what is multithreading, I don't need to record. Baidu is a pile of random notes, mainly recording notes that are useful to me. Here I mainly use the threading module

Multithreading (review)

General creation method (not commonly used)

Note: the args parameter is a tuple. If there is only one parameter, don't forget the comma after it

import threading
import time

def run(n):
    print("hello", n)
    time.sleep(1)

if __name__ == '__main__':
    t1 = threading.Thread(target=run, args=("threading1",))
    t2 = threading.Thread(target=run, args=("threading2",))
    t1.start()
    t2.start()

Custom thread (inherited threading.Thread)

Essence: Refactoring run method

import threading
import time

class MyThread(threading.Thread):
    def __init__(self, item):
        threading.Thread.__init__(self)
        self.item = item

    def run(self):
        print("threadingTask", self.item)
        time.sleep(1)
       

if __name__ == "__main__":
    t1 = MyThread("threading1")
    t2 = MyThread("threading2")
    t1.start()
    t2.start()

Advanced knowledge

Daemons

Here, setDaemon(True) is used to turn all the sub threads into the main thread's Guardian threads, so when the main process ends, the sub threads will also end. So when the main thread ends, the whole program exits.

import threading
import time


class MyThread(threading.Thread):
    def __init__(self, item,t):
        threading.Thread.__init__(self)
        self.item = item
        self.t = t

    def run(self):
        print("threadingTask", self.item)
        time.sleep(self.t)
        print(self.item+"end")


if __name__ == "__main__":
    t1 = MyThread("threading1",1)
    t2 = MyThread("threading2",10)
    t3 = MyThread("threading3",100)
    t2.setDaemon(True)
    t3.setDaemon(True)
    t1.start()
    t2.start()
    t3.start()

Operation result:

After setting the daemons, when the main thread ends, the sub thread will end immediately and will not execute any more

Let the main thread wait for the child thread to end

Of course, setting thread daemons in the following example is of little significance, just to emphasize that setting subprocesses as daemons must be set before start()

import threading
import time


class MyThread(threading.Thread):
    def __init__(self, item, t):
        threading.Thread.__init__(self)
        self.item = item
        self.t = t

    def run(self):
        print("threadingTask", self.item)
        time.sleep(self.t)
        print(self.item + "end")


if __name__ == "__main__":
    t1 = MyThread("threading1", 1)
    t2 = MyThread("threading2", 2)
    t3 = MyThread("threading3", 3)
    t2.setDaemon(True)
    t3.setDaemon(True)
    t1.start()
    t2.start()
    t3.start()
    t1.join()
    t2.join()
    t3.join()

Thread sharing global variables

Thread is the execution unit of a process, and process is the smallest unit of resources allocated by the system, so multithreads in the same process share resources. And threads can share global variables. I don't know what to say. I copied a code online for review
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()

Thread synchronization

The following parts are from Rookie tutorial
If multiple threads modify a data together, unexpected results may appear. In order to ensure the correctness of data, it is necessary to synchronize multiple threads. The advantage of multithreading is that you can run multiple tasks at the same time (at least it feels like this). However, when threads need to share data, there may be data synchronization problems.

Consider a situation where all elements in a list are 0, the thread "set" changes all elements from back to front to 1, and the thread "print" reads the list from front to back and prints.

Then, when the thread "set" starts to change, the thread "print" will print the list, and the output will be half 0 and half 1, which is the data out of sync. To avoid this situation, the concept of lock is introduced.

There are two states of a lock - locked and unlocked. Every time a thread such as "set" wants to access shared data, it must obtain lock first; if another thread such as "print" has obtained lock, then let thread "set" pause, that is, synchronous block; after thread "print" access is completed and the lock is released, let thread "set" continue.

After this kind of processing, when printing the list, you can either output 0 or 1 completely, and there will be no awkward scene of half 0 and half 1 again.

mutex

#!/usr/bin/python3

import threading
import time


class myThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print("Open thread: " + self.name)
        # Get lock for thread synchronization
        threadLock.acquire()
        print_time(self.name, self.counter, 3)
        # Release lock, open next thread
        threadLock.release()


def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1


threadLock = threading.Lock()
threads = []

# Create a new thread
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# Start a new thread
thread1.start()
thread2.start()

# Add thread to thread list
threads.append(thread1)
threads.append(thread2)

# Wait for all threads to complete
for t in threads:
    t.join()
print("Exit main thread")

Recursive lock

The usage as like as two peas of RLcok class is Lock class, but it supports nesting. RLcok classes are usually used when multiple locks are not released.

#!/usr/bin/python3

import threading
import time


class myThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print("Open thread: " + self.name)
        # Get lock for thread synchronization
        threadLock.acquire()
        print_time(self.name, self.counter, 3)
        # Release lock, open next thread
        threadLock.release()


def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1


threadLock = threading.RLock()
threads = []

# Create a new thread
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# Start a new thread
thread1.start()
thread2.start()

# Add thread to thread list
threads.append(thread1)
threads.append(thread2)

# Wait for all threads to complete
for t in threads:
    t.join()
print("Exit main thread")

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

#!/usr/bin/python3

import threading
import time


class myThread(threading.Thread):
    def __init__(self, threadID, name, counter, semaphore):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter
        self.semaphore = semaphore

    def run(self):
        print("Open thread: " + self.name)
        semaphore.acquire()
        print_time(self.name, self.counter, 3)
        semaphore.release()


def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1


semaphore = threading.BoundedSemaphore(2)
threads = []

for i in range(3):
    t = myThread(i + 1, f"Thread-{i + 1}", i + 1,semaphore)
    threads.append(t)

for i in threads:
    i.start()


while threading.active_count() != 1:
    pass
else:
    print('-----all threads done-----')

Event (event class)

This part is from [blog], thank you for your blog( https://www.cnblogs.com/luyuze95/p/11289143.html#threading%E6%A8%A1%E5%9D%97)
python thread events are used by the main thread to control the execution of other threads. Event is a simple thread synchronization object, which mainly provides the following methods:

clear set flag to False
Set set flag to True
is_set to determine whether the flag is set
wait will listen to the flag all the time. If no flag is detected, it will be blocked all the time
Event handling mechanism: a "flag" is defined globally. When the flag value is "False", then event.wait() will block, when the flag value is "True", then event.wait() is no longer blocked.

#Using Event class to simulate traffic light
import threading
import time

event = threading.Event()


def lighter():
    count = 0
    event.set()     #The initial value is green
    while True:
        if 5 < count <=10 :
            event.clear()  # Red light, clear flag
            print("\33[41;1mred light is on...\033[0m")
        elif count > 10:
            event.set()  # Green light, set flag bit
            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():      #Determine whether flag bit 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()

Posted by hitman6003 on Mon, 15 Jun 2020 23:23:34 -0700