Explain multithreading in python

Posted by Dodon on Thu, 30 Dec 2021 18:42:38 +0100


This article will directly give several examples to see how python implements multithreading. Previously in c + + An article In, the concept of multithreading is introduced. We won't continue to explain it here. If you are interested, you can click the link to understand it.

Python 3 uses two standard libraries_ Thread and threading provide support for threads because_ Thread is only compatible with Python 2 thread module, so it is recommended to use threading module to implement multithreading.

1. Introduction to some functions of threading

methodexplain
threading.currentThread()Returns the current thread variable
threading.enumerate()Returns a list of running threads
threading.activeCount()Returns the number of running threads, with the same result as len(threading.enumerate()).

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

methodexplain
run()Method used to represent thread activity.
start()Start thread activity.
join([time])Wait until the thread aborts. Abort mode: exit normally or throw an unhandled exception - or an optional timeout occurs.
isAlive()Returns whether the thread is active.
getName()Returns the thread name. The first sub thread name is Thread-1, and the main thread name is MainThread
setName()Set the thread name.

The following examples illustrate these functions. If you don't understand them, it doesn't matter. You can skip them temporarily and look back after learning all the contents.

import threading
import time

def thread_1(num):
    for i in range(num):
        #print(threading.currentThread()) #Current thread variable
        time.sleep(0.5)

def thread_2(num):
    for i in range(num):
        #print(threading.currentThread()) #Current thread variable
        time.sleep(0.5)
        
def main():
	#args is the parameter corresponding to the function and exists in the form of tuple
    t_thread_1 = threading.Thread(target=thread_1, args=(5,))
    t_thread_2 = threading.Thread(target=thread_2, args=(6,))
    print("t_thread_1 isAlive(): ", t_thread_1.isAlive())  # Survival, False
    t_thread_1.start()#start-up
    t_thread_2.start()#start-up

    print("threading.enumerate(): ", threading.enumerate()) #The scope is a list
    print("threading.activeCount(): ", threading.activeCount()) #There are currently 3, including a main thread

    #Print information about a thread
    print("t_thread_1 isAlive(): ",t_thread_1.isAlive())#Survival, True
    print("t_thread_1 getName(): ", t_thread_1.getName())  # Get thread name Thread-1
    print("t_thread_2 getName(): ", t_thread_2.getName())  # Get thread name Thread-2

    t_thread_1.join()
    t_thread_2.join()
    print("t_thread_1 isAlive(): ", t_thread_1.isAlive())  # Survival, False
    print("Main thread end")

if __name__ == '__main__':
    main()

2. Thread implementation - functions and classes

(1) Multithreading in function mode

import threading
import time

def thread_1(num):
    for i in range(num):
        print("Child thread 1:%d" % i)
        time.sleep(0.5)

def thread_2(num):
    for i in range(num):
        print("Sub thread 2:%d" % i)
        time.sleep(0.5)
        
def main():
	#args is the parameter corresponding to the function and exists in the form of tuple
    t_thread_1 = threading.Thread(target=thread_1, args=(5,))
    t_thread_2 = threading.Thread(target=thread_2, args=(6,))
    t_thread_1.start()#start-up
    t_thread_2.start()#start-up

if __name__ == '__main__':
    main()

(2) Class to implement multithreading

Steps:

  1. Import threading package
  2. Create a class that inherits threading Thread class, overriding the run method
  3. Create a class object and call the start() method to start the thread
import threading
import time

class MyThread(threading.Thread):
    def run(self): #rewrite
        for i in range(3):
            time.sleep(1)
            #self.name: the name of the thread starts from Thread-1
            msg = "I'm "+self.name+' @ '+str(i)
            print(msg)

def main():
    for i in range(5):#Create 5 threads
        t = MyThread()
        t.start()

if __name__ == '__main__':
    main()

3. Daemon thread and synchronization thread

(1) Non daemon thread - default thread

Non daemon thread: after the main thread executes all programs, the main thread does not exit, that is, it will not be destroyed until all sub threads complete their tasks. By default, the threads generated by python are non daemon threads.

When the thread is set to setDaemon(False), it indicates that the thread is a non daemon thread. The default is False. If it is set to True, it is a daemon thread.

In the following code, the child thread sets a delay, and the main thread will wait for the child thread to end.

import threading
import time

def my_thread():
    time.sleep(2)#Delay 2s
    print('---Child thread end---')

def main():
    t1 = threading.Thread(target=my_thread)
    t1.start()
    print('---Main thread---end')

if __name__ == '__main__':
    main()

Output:

---Main thread---end
---Child thread end---

(2) Daemon thread

The daemon thread, that is, when the main thread ends, the child thread will also die. To establish a daemon thread, you only need to set setDaemon(True).

Take the following code as an example. Because the child thread is set as the daemon thread and the delay of 2s is set in the child thread, the print of the child thread will not run after the main thread ends.

import threading
import time

def my_thread():
    time.sleep(2)
    print('---Child thread end---')

def main():
    t1 = threading.Thread(target=my_thread)
    t1.setDaemon(True)
    t1.start()
    print('---Main thread---end')

if __name__ == '__main__':
    main()

Output:

---Main thread---end

(3) Synchronous thread (join)

When the main thread encounters a synchronous thread, that is, when it encounters a join(), it will wait (that is, the main thread enters a blocked state and cannot move on) for all synchronous threads to complete execution, and then the main thread will continue to move on.

import threading
import time

def my_thread_1():
    for i in range(2):#Run for 2s
        print('Sub thread 1 sleep{}second'.format(i + 1))
        time.sleep(1)
    print('End of sub thread 1!!!')

def my_thread_2():
    for i in range(5):#Operate for 5s
        print('Sub thread 2 sleep{}second'.format(i + 1))
        time.sleep(1)
    print('End of sub thread 2!!!')

def main():
    t1 = threading.Thread(target=my_thread_1)
    t1.start()

    t2 = threading.Thread(target=my_thread_2)
    t2.start()

    t1.join()#Wait for child thread 1 to end
    t2.join()#Wait for child thread 2 to end

    print('End of main thread!!!')

if __name__ == '__main__':
    main()

Output:

Sub thread 1 sleep for the first second
 Sub thread 2 sleep for the first second
 Subthread 1 sleep second
 Sub thread 2 sleep second
 End of sub thread 1!!!
Sub thread 2 sleep for the third second
 Sub thread 2 sleep for the 4th second
 Sub thread 2 sleep for the fifth second
 End of sub thread 2!!!
End of main thread!!!

(4) Non daemon thread + synchronous thread

General situation: when we set a child thread as a non daemon thread and set it as a synchronous thread, that is, the synchronous thread join() mentioned above is the same. The main thread will wait for all synchronous threads to execute before proceeding, because by default, the created thread is a non daemon thread.

The second case: if the timeout parameter is added to the join, the main thread will wait for the timeout time of the child thread, and then continue to execute. The main thread will not exit after execution, but wait for the child thread to complete the task. Details can be found in the next section.

import threading
import time

def my_thread():
    for i in range(2):
        print('Sub thread 1 sleep{}second'.format(i + 1))
        time.sleep(1)
    print('End of child thread!!!')

def main():
    t1 = threading.Thread(target=my_thread)
    t1.setDaemon(False)
    t1.start()
    t1.join()
    #t1.join(timeout=1)#join sets timeout in seconds
    print('End of main thread!!!')

if __name__ == '__main__':
    main()

Output:

Sub thread 1 sleep for the first second
 Subthread 1 sleep second
 End of child thread!!!
End of main thread!!!

(5) Daemon + sync thread join

General situation: if it is a daemon thread and the timeout is not set in the join, the main thread will wait for the child thread to complete the task before continuing to execute.

The second case: if the timeout parameter is set for the join, after waiting for the time of the sub thread, the main thread continues to proceed. After the main thread finishes its task, it is found that the sub thread is still a guard thread, but at this time, the sub thread has not completed its task, and the main thread will not wait for it, but will be destroyed directly and automatically.

import threading
import time

def thread1():
    for i in range(5):
        print('Sub thread sleep{}second'.format(i + 1))
        time.sleep(1)
    print('End of child thread!!!')

def main():
    t1 = threading.Thread(target=thread1)
    t1.setDaemon(True)
    t1.start()
    t1.join(timeout=1)#Set timeout in seconds
    print('End of main thread!!!')

if __name__ == '__main__':
    main()

Output:

Sub thread sleep 1st second
 End of main thread!!!

4. Mutex

Because threads are randomly scheduled, if multiple threads operate on an object at the same time, if the object is not well protected, the program result will be unpredictable. Therefore, we also call it "thread unsafe". In order to prevent the above situation, a mutually exclusive Lock appears.

Simple thread synchronization can be realized by using Lock and Rlock of Thread objects. Both objects have acquire and release methods. For data that needs to be operated by only one thread at a time, its operation can be placed between acquire and release methods.

import threading
import time

class myThread (threading.Thread):
    def __init__(self, threadID, name, my_time):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.my_time = my_time
    def run(self):
        print ("Open thread: " + self.name)
        # Get lock for thread synchronization
        threadLock.acquire()
        print_time(self.name, self.my_time, 5)#Each run 5 times
        # Release the lock and start the next thread
        threadLock.release()

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

threadLock = threading.Lock()
threads = []

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

# Open 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")

Output:

Open thread: Thread-1
 Open thread: Thread-2
Thread-1: Wed Dec 29 14:41:14 2021
Thread-1: Wed Dec 29 14:41:15 2021
Thread-1: Wed Dec 29 14:41:16 2021
Thread-1: Wed Dec 29 14:41:17 2021
Thread-1: Wed Dec 29 14:41:18 2021
Thread-2: Wed Dec 29 14:41:20 2021
Thread-2: Wed Dec 29 14:41:22 2021
Thread-2: Wed Dec 29 14:41:24 2021
Thread-2: Wed Dec 29 14:41:26 2021
Thread-2: Wed Dec 29 14:41:28 2021
 Exit main thread

Topics: Python Multithreading