thread

Threads are similar to processes, except that they execute under the same process and share the same context.

A thread consists of three parts: start execution, order execution and end execution. It has an instruction pointer, which is used to record the current running context. They can be preempted (interrupted) and suspended (sleep), a practice known as concession.

Each thread in a process shares the same data space with the main thread, so it is more convenient to share and communicate information with threads compared with independent processes. Threads are typically executed concurrently, so this concurrency and data sharing mechanism makes multi-task collaboration possible.

Note that true concurrency is not possible on a single-core CPU. The execution of threads in a process is planned in such a way that each thread executes for a short period of time and then gives way to other threads, sometimes communicating the results with other threads.

Sharing is also risky. If two or more threads access the same piece of data, the results may be inconsistent due to the order in which the data is accessed.

Another problem is that threads do not give fair execution time, because some functions block until completion, and if multithreading is not modified, CPU time will be allocated to these greedy functions.

Use threads in Python

The case where threads are not used

Here’s an example of how a program executes without threads:

from time import ctime, sleep


def loop0() :
    print('loop0 start at: ', ctime())
    sleep(4)
    print('loop0 done at: ', ctime())


def loop1() :
    print('loop1 start at: ', ctime())
    sleep(2)
    print('loop1 done at: ', ctime())


def main() :
    print('starting at ', ctime())
    loop0()
    loop1()
    print('all done at ', ctime())


if __name__ == '__main__':
    main()
Copy the code

Running results:

starting at  Mon Jun  7 16:48:51 2021
loop0 start at:  Mon Jun  7 16:48:51 2021
loop0 done at:  Mon Jun  7 16:48:55 2021
loop1 start at:  Mon Jun  7 16:48:55 2021
loop1 done at:  Mon Jun  7 16:48:57 2021
all done at  Mon Jun  7 16:48:57 2021
Copy the code

From the running result of the program, the program is executed in order without the use of multithreading. Function loop0 has a sleep time of 4 seconds, while function loop1 has a running time of 2 seconds. Therefore, it takes 6 seconds after the program is run.

threading

Python provides several modules to manage and create threads. The Threading module is one of them. The Thread object in the threading module represents an object that executes a Thread.

Here is an example of using threads to execute a program:

from time import sleep, ctime
import threading


def loop0() :
    print('loop0 start at: ', ctime())
    sleep(4)
    print('loop0 done at ', ctime())


def loop1() :
    print('loop1 start at: ', ctime())
    sleep(2)
    print('loop1 done at ', ctime())


def main() :
    print('start at ', ctime())
    t1 = threading.Thread(target=loop0)	# create thread
    t2 = threading.Thread(target=loop1)
    t1.start()	# start thread
    t2.start()
    print('All done at ', ctime())


if __name__ == '__main__':
    main()
Copy the code

Running results:

start at  Mon Jun  7 17:28:51 2021
loop0 start at:  Mon Jun  7 17:28:51 2021
loop1 start at: All done at   Mon Jun  7 17:28:51 2021Mon Jun  7 17:28:51 2021

loop1 done at  Mon Jun  7 17:28:53 2021
loop0 done at  Mon Jun  7 17:28:55 2021
Copy the code

When I execute t1 and T2, the code will continue to execute, so I will see All done at… The operation is complete.

Seeing the results of this run validates the thread execution plan described above.

One thing to note:t1.start()This means that a thread can be started, but it depends on the CPU when the thread executes.

Overrides the run method of Thread

import threading
from time import ctime, sleep


class MyThread(threading.Thread) :
    def __init__(self, thread_name) :
        super(MyThread, self).__init__(name=thread_name)
        self.thread_name = thread_name

    def run(self) :
        print('%s start at ' % self.thread_name, ctime())
        sleep(4)
        print('%s end at ' % self.thread_name, ctime())


def main() :
    print('Start at ', ctime())
    t1 = MyThread('loop0')
    t2 = MyThread('loop1')
    t1.start()
    t2.start()
    print('All done at ', ctime())


if __name__ == '__main__':
    main()
Copy the code

In the above code, we implement multithreading by overriding the run() method of the Thread object.

The running result is as follows:

Start at  Mon Jun  7 19:09:34 2021
loop0 start at  Mon Jun  7 19:09:34 2021
loop1 start at  Mon Jun  7 19:09:34 2021
All done at  Mon Jun  7 19:09:34 2021
loop1 end at  loop0 end at  Mon Jun  7 19:09:38 2021
Mon Jun  7 19:09:38 2021
Copy the code

If it is run in order, the running time of the program is at least 8 seconds, but because multithreading can be concurrent or parallel execution, so it only takes 4 seconds to complete the execution, greatly speeding up the efficiency of the program.

All of the above programs use the threading.Thread class directly or indirectly.

Next, you can combine the above two ways of creating threads.

import threading
from time import sleep, ctime


class MyThread(threading.Thread) :
    def __init__(self, thread_name, target=None) :
        super(MyThread, self).__init__(name=thread_name, target=target, args=(thread_name, ))
        self.thread_name = thread_name

    def run(self) :
        super(MyThread, self).run()


def loop0(arg) :
    print('%s start at ' % arg, ctime())
    sleep(4)
    print('%s end at ' % arg, ctime())


def loop1(arg) :
    print('%s start at ' % arg, ctime())
    sleep(2)
    print('%s end at ' % arg, ctime())


def main() :
    print('start at ', ctime())
    t1 = MyThread('loop0', loop0)
    t2 = MyThread('loop1', loop1)
    t1.start()
    t2.start()
    print('All done at', ctime())



if __name__ == '__main__':
    main()
Copy the code

The running result is as follows:

start at  Mon Jun  7 19:37:46 2021
loop3 start at  Mon Jun  7 19:37:46 2021
loop1 start at  Mon Jun  7 19:37:46 2021All done at Mon Jun  7 19:37:46 2021

loop1 end at  Mon Jun  7 19:37:48 2021
loop3 end at  Mon Jun  7 19:37:50 2021
Copy the code

Similarly, because of the concurrency and parallelism of threads, two threads can be executed at the same time, speeding up the running speed of programs.

threading.Thread

class threading.``Thread(group=None, target=None, name=None, args=(), kwargs={}, ***, daemon=None)

This constructor must be called with a keyword argument. The parameters are as follows:

Group should be None; Reserved for future extension of the ThreadGroup class implementation.

Target is the callable object used for the run() method call. The default is None, which means no methods need to be called.

Name is the thread name. By default, a unique name is made up of the “thread-n” format, where N is a small decimal number.

Args is a tuple of arguments used to call the target function. The default is ().

Kwargs is a dictionary of keyword arguments used to call the target function. The default is {}.

If it is not None, the daemon argument explicitly sets whether the thread is in daemon mode. If None (the default), the thread inherits the daemon mode properties of the current thread.

If a subtype overloads a constructor, it must ensure that it invokes the base class constructor (thread.__init__ ()) before doing anything.

Here are the Thread object methods and attributes provided by threading.Thread:

Start () : After the thread is created, it is started by the start() method and waits for the CPU to schedule it in preparation for the run function to execute.

Run () : The entry function that the thread starts executing. The user-written target() function is called in the function body.

Join () : blocks and suspends the thread calling this function until the called thread completes or times out. This method is usually called in the main thread and waits for the other threads to finish executing.

Multithreaded execution

After several threads are created in the main thread, there is no coordination or synchronization between them, and every thread except the main thread is executed from run.

Create thread block

We can block the main thread using the join method and wait for the thread it created to complete.

import threading
import time


def loop0() :
    print('loop0 start at ', time.ctime())
    time.sleep(4)
    print('loop0 end at ', time.ctime())


def loop1() :
    print('loop1 start at ', time.ctime())
    time.sleep(4)
    print('loop1 end at ', time.ctime())


def main() :
    print('start at '. time.ctime()) t1 = threading.Thread(target=loop0) t2 = threading.Thread(target=loop1) t1.start() t2.start() t1.join() t2.join()print('All done', time.ctime())


if __name__ == '__main__':
    main()
Copy the code

Running results:

start at Mon Jun 7 20:37:08 2021 loop0 start at Mon Jun 7 20:37:08 2021 loop1 start at Mon Jun 7 20:37:08 2021 loop0 end  at Mon Jun 7 20:37:12 2021 loop1 end at Mon Jun 7 20:37:12 2021 All done Mon Jun 7 20:37:12 2021Copy the code

Daemons and non-daemons

When a new thread is created, the main thread inherits its thread properties from its parent thread. The main thread is a normal non-daemon thread, and any threads it creates are non-daemons by default.

The main thread that cannot exit

Often we need to create threads to perform a routine task or to provide a service. The most common garbage collector is a garbage collector that runs in the background and reclaims garbage memory that is no longer used by the program.

By default, new threads usually generate non-daemons or normal threads, and if the new thread is running, the main thread will wait forever and the program cannot exit.

Next, we’ll use an example to briefly illustrate the case where the main thread cannot exit:

import threading
import time


def kitchen_cleaner() :
    while True:
        print("Olivia cleaned the kitchen.")
        time.sleep(1)


if __name__ == '__main__':
    olivia = threading.Thread(target=kitchen_cleaner)
    olivia.start()

    print('Barron is cooking... ')
    time.sleep(0.6)
    print('Barron is cooking... ')
    time.sleep(0.6)
    print('Barron is cooking... ')
    time.sleep(0.6)
    print('Barron is done! ')
Copy the code

Running results:

Olivia cleaned the kitchen.
Barron is cooking...
Barron is cooking...
Olivia cleaned the kitchen.
Barron is cooking...
Barron is done!
Olivia cleaned the kitchen.
Olivia cleaned the kitchen.
Olivia cleaned the kitchen.
Copy the code

Daemon thread

In the above code we define the Kitchen_cleaner function, which represents a garbage collector that periodically cleans garbage in the background.

For the Kitchen_cleaner function, it is executed once per second. Next, I set Olivia as the daemon thread.

import threading
import time


def kitchen_cleaner() :
    while True:
        print("Olivia cleaned the kitchen.")
        time.sleep(1)


if __name__ == '__main__':
    olivia = threading.Thread(target=kitchen_cleaner)
    olivia.daemon = True
    olivia.start()

    print('Barron is cooking... ')
    time.sleep(0.6)
    print('Barron is cooking... ')
    time.sleep(0.6)
    print('Barron is cooking... ')
    time.sleep(0.6)
    print('Barron is done! ')

Copy the code

All you need to do is add fauxlix.daemon = True.

Running results:

Olivia cleaned the kitchen.
Barron is cooking...
Barron is cooking...
Olivia cleaned the kitchen.
Barron is cooking...
Barron is done!
Copy the code

When the program is run again, the main program is finished and no new threads are running, the program exits.

Note that:

  • The daemon thread must be set before the thread is started, or a runtime error will occur.
  • Daemons do not exit normally like other normal threads. When all non-daemons in a program have finished executing, any remaining daemons are discarded when Python exits, so design daemons to ensure that the main program exits without any impact on the daemons.