Advanced Python learning [1] - multithreading

preface

Reference: rookie tutorial
Rookie tutorial

1. Introduction to multithreading

(1) Using threads, you can put the tasks in the program that occupy a long time to the background for processing;
(2) In the implementation of some waiting tasks, such as user input, file reading and writing, network sending and receiving data, threads are more useful. In this case, we can release some precious resources, such as memory occupation and so on;
(3) Using threads, you can put the tasks in the program that occupy a long time to the background for processing;
(4) The running speed of the program may be accelerated;
(5) The advantage of multithreading is that it can run multiple tasks at the same time (at least it feels like this). However, when threads need to share data, there may be a problem of data synchronization. (see III for details)

I_ thread module

(1) Preface: because the thread module in Python 3 has been abandoned. Users can use threading module instead, but Python 3 renames thread to==_ thread==.
(2) Key functions:_ thread.start_new_thread() to generate a new thread;

Format:_ thread.start_new_thread ( function, args[, kwargs] )

  • Function - thread function.
  • args - the parameter passed to the thread function. It must be of type tuple.
  • kwargs - optional parameter.
    Notes: the second parameter must be a tuple, and the number of elements in the tuple is optional.

(3) Example:

2, threading module

(1) threading module in addition to owning_ In addition to all methods of thread module, other methods are also provided:

  • threading.currentThread(): returns the current thread variable.
  • threading.enumerate(): returns a list of running threads. Running refers to the thread after starting and before ending, excluding the thread before starting and after termination.
  • threading.activeCount(): returns the number of running threads, with the same result as len(threading.enumerate()).

(2) In addition to using methods, the thread module also provides thread class to process threads. Thread class provides the following methods:

  • run(): a method used to represent thread activity.
  • start(): start thread activity.
  • join([time]): wait until the thread aborts. This blocks the calling thread until the thread's join() method is called to abort - exit normally or throw an unhandled exception - or an optional timeout occurs.
  • isAlive(): Returns whether the thread is active.
  • getName(): returns the thread name.
  • setName(): set the thread name.

(3) The threading module creates a thread

import threading
import time

exitFlag = 0


class MyThread(threading.Thread):
    def __init__(self, thread_id, name, counter):#Class constructor
        threading.Thread.__init__(self)
        self.thread_id = thread_id
        self.name = name
        self.counter = counter

    def run(self):
        print("Start thread:" + self.name)
        print_time(self.name, self.counter, 5)
        print("Exit thread:" + self.name)


def print_time(thread_name, delay, counter):
    while counter:
        if exitFlag:
            thread_name.exit()
        time.sleep(delay)
        print("%s: %s" % (thread_name, time.ctime(time.time())))  # Format output
        counter -= 1


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

# Start a new thread
thread1.start()
thread2.start()
thread1.join()  # Wait for thread 1 to end
thread2.join()  # Wait for thread 2 to end
print("Exit main thread")

(4) About overriding the run() method in the Thread class
<1> Because the Thread class calls the start method every time. Through the start method, the run method will be called every time;
<2> Therefore, in the self built subclass inherited from Thread class, the user can add some personalized operations by rewriting the run() method every time;

(5) About The role of the join() method
<1> In multithreading, the completion order of threads depends on the amount of tasks and execution time, so it is difficult to determine which thread runs first;
<2>. The join () method can be used in After start, it is used to control the running sequence of threads. It can block sub threads and execute other programs after the execution of sub threads.
Detailed reference

3, Thread synchronization

(1) If multiple threads modify a data together, unpredictable results may occur. In order to ensure the correctness of the data, multiple threads need to be synchronized;

1. Mission

(1) Consider this situation: all elements in a list are 0, the thread "set" changes all elements to 1 from back to front, and the thread "print" is responsible for reading and printing the list from front to back.
(2) 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 asynchrony of data. In order to avoid this situation, the concept of lock is introduced.
(3) Solution: in order to avoid this situation, the concept of lock is introduced. There are two states of a lock - locked and unlocked. Whenever a thread, such as "set", wants to access shared data, it must first obtain a lock; If another thread, such as "print", has been locked, let the thread "set" pause, that is, synchronization blocking; Wait until the thread "print" accesses and releases the lock, and then let the thread "set" continue.

Guess: then I join blocks the thread through join specifies the thread execution order. Is it also a side thread synchronization method.

2. Examples

import threading
import time


class MyThread(threading.Thread):
    def __init__(self, thread_id, name, counter):
        threading.Thread.__init__(self)
        self.thread_id = thread_id
        self.name = name
        self.counter = counter

    def run(self):
        print("Open thread: " + self.name)
        # Get lock for thread synchronization
        threadLock.acquire()  # Variables / objects that call external programs in a class
        print_time(self.name, self.counter, 3)  # Functions that call external programs in a class
        # Release the lock and start the next thread
        threadLock.release()


def print_time(thread_name, delay, counter):
    while counter:
        time.sleep(delay)
        print("%s: %s" % (thread_name, 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")

3, Thread priority (Queue module)

  • Queue.qsize() returns the size of the queue
  • Queue.empty() returns True if the queue is empty, otherwise False
  • Queue.full() returns True if the queue is full, otherwise False
  • Queue.full corresponds to maxsize size
  • Queue.get([block[, timeout]]) get queue, timeout wait time
  • Queue.get_nowait() is equivalent to queue get(False)
  • Queue.put(item) writes to the queue and timeout the waiting time
  • Queue.put_nowait(item) is equivalent to queue put(item, False)
  • Queue.task_done() after completing a task, queue task_ The done () function sends a signal to the queue where the task has been completed
  • Queue.join() actually means waiting until the queue is empty before doing anything else

1. About task_ Role of done

If the thread fetches from the queue every time, but does not execute the task_done(), the join cannot judge whether the queue has ended or not. Executing the last join() cannot wait for the result and will hang all the time.
It can be understood that every task_done Deletes one element from the queue at a time, so that it can judge whether the queue ends according to whether the queue length is zero at the last join, so as to execute the main thread.
See details

2. Code example

import queue
import threading
import time

exitFlag = 0


class MyThread(threading.Thread):
    def __init__(self, thread_id, name, q):
        threading.Thread.__init__(self)
        self.thread_id = thread_id
        self.name = name
        self.q = q

    def run(self):
        print("Open thread:" + self.name)
        process_data(self.name, self.q)
        print("Exit thread:" + self.name)


def process_data(thread_name, q):
    while not exitFlag:  # If you don't receive the exit signal, you will always be in this loop
        queueLock.acquire()
        if not workQueue.empty():  # If the queue is not empty, it returns False. If not is added, it is True. That is, if is not empty
            data = q.get()
            queueLock.release()
            print("%s processing %s" % (thread_name, data))
        else:  # Before put ting the data into the queue, execute else and do nothing but occupy the space.
            queueLock.release()
        time.sleep(1)


threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)  # Construction method of queue. The function signature is Queue(maxsize=0), where maxsize sets the size of the queue.
threads = []
thread_ID = 1

# Create a new thread
for tName in threadList:
    thread = MyThread(thread_ID, tName, workQueue) # The third is a list of variable types.
    thread.start()
    threads.append(thread)
    thread_ID += 1

# Fill queue
# The loop used to fill the queue must lock the thread before filling the queue again, and then release it after filling the queue. In order to avoid fetching out as soon as the content is put into the queue during thread processing.
# Therefore, the purpose of locking the thread is to execute the thread after the queue is filled.
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
    # Queue.put(item, block=True, timeout=None): put data into the queue. If it is Full, blocking = False will directly report the Full exception.
    # If blocking = True, it means wait a while. timeout must be 0 or positive. None means to wait all the time, 0 means to wait, and a positive number n means to wait for N seconds and cannot be saved. A Full exception is reported.
queueLock.release()

# Waiting for the queue to clear
while not workQueue.empty():  # If the queue is empty, jump out of the loop and execute the following statement. If it is not empty, return False, add not to change to True, and execute the loop body.
    pass  # Do nothing, just occupy a seat. Lest the contents of this loop body be empty.

# Notify the thread when to exit
exitFlag = 1

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

Keywords: Python

Added by teng84 on Fri, 28 Jan 2022 05:12:16 +0200