Concurrency In Python 简明教程

Implementation of Threads

在本章中,我们将学习如何在 Python 中实现线程。

In this chapter, we will learn how to implement threads in Python.

Python Module for Thread Implementation

Python 线程有时被称为轻量级进程,因为线程占用的内存远少于进程。线程允许一次执行多个任务。在 Python 中,我们有以下两个模块在程序中实现线程——

Python threads are sometimes called lightweight processes because threads occupy much less memory than processes. Threads allow performing multiple tasks at once. In Python, we have the following two modules that implement threads in a program −

  1. *<_thread>*module

  2. *<threading>*module

这两个模块的主要差别在于 <_thread> 模块将线程作为函数处理,而 <threading> 模块将每个线程都作为对象处理,并以面向对象的方式实现它。此外, <_thread>*module is effective in low level threading and has fewer capabilities than the *<threading> 模块。

The main difference between these two modules is that <_thread> module treats a thread as a function whereas, the <threading> module treats every thread as an object and implements it in an object oriented way. Moreover, the <_thread>*module is effective in low level threading and has fewer capabilities than the *<threading> module.

<_thread> module

在早期版本的 Python 中,我们曾使用过 <thread> 模块,但它很长时间都被视为“已弃用”。我们鼓励用户使用 <threading> 模块。因此,在 Python 3 中不再提供 “thread” 模块。为了与 Python3 中向后不兼容,它已被重命名为 <_thread>

In the earlier version of Python, we had the <thread> module but it has been considered as "deprecated" for quite a long time. Users have been encouraged to use the <threading> module instead. Therefore, in Python 3 the module "thread" is not available anymore. It has been renamed to "<_thread>" for backwards incompatibilities in Python3.

要借助 <_thread> 模块生成新线程,我们需要调用它的 start_new_thread 方法。此方法的工作原理可通过以下语法得到理解 −

To generate new thread with the help of the <_thread> module, we need to call the start_new_thread method of it. The working of this method can be understood with the help of following syntax −

_thread.start_new_thread ( function, args[, kwargs] )

这里 -

Here −

  1. args is a tuple of arguments

  2. kwargs is an optional dictionary of keyword arguments

如果我们想要在不传递参数的情况下调用函数,那么需要在 args 中使用一个空元组参数。

If we want to call function without passing an argument then we need to use an empty tuple of arguments in args.

此方法调用会立即返回,子线程会启动,并使用已传递的参数列表(如果有的话)调用函数。当函数返回时,该线程会终止。

This method call returns immediately, the child thread starts, and calls function with the passed list, if any, of args. The thread terminates as and when the function returns.

Example

以下是使用 <_thread> 模块生成新线程的示例。此处使用了 start_new_thread() 方法。

Following is an example for generating new thread by using the <_thread> module. We are using the start_new_thread() method here.

import _thread
import time

def print_time( threadName, delay):
   count = 0
   while count < 5:
      time.sleep(delay)
      count += 1
      print ("%s: %s" % ( threadName, time.ctime(time.time()) ))

try:
   _thread.start_new_thread( print_time, ("Thread-1", 2, ) )
   _thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
   print ("Error: unable to start thread")
while 1:
   pass

Output

以下的输出将帮助我们了解如何借助 <_thread> 模块生成新线程。

The following output will help us understand the generation of new threads bwith the help of the <_thread> module.

Thread-1: Mon Apr 23 10:03:33 2018
Thread-2: Mon Apr 23 10:03:35 2018
Thread-1: Mon Apr 23 10:03:35 2018
Thread-1: Mon Apr 23 10:03:37 2018
Thread-2: Mon Apr 23 10:03:39 2018
Thread-1: Mon Apr 23 10:03:39 2018
Thread-1: Mon Apr 23 10:03:41 2018
Thread-2: Mon Apr 23 10:03:43 2018
Thread-2: Mon Apr 23 10:03:47 2018
Thread-2: Mon Apr 23 10:03:51 2018

<threading> module

<threading> 模块以面向对象的方式实现,并将每个线程都作为对象处理。因此,它为线程提供了比 <_thread> 模块更强大、更高级别的支持。此模块包含在 Python 2.4 中。

The <threading> module implements in an object oriented way and treats every thread as an object. Therefore, it provides much more powerful, high-level support for threads than the <_thread> module. This module is included with Python 2.4.

Additional methods in the <threading> module

<threading> 模块包含 <_thread> 模块的所有方法,但也提供了附加方法。附加方法如下 −

The <threading> module comprises all the methods of the <_thread> module but it provides additional methods as well. The additional methods are as follows −

  1. threading.activeCount() − This method returns the number of thread objects that are active

  2. threading.currentThread() − This method returns the number of thread objects in the caller’s thread control.

  3. threading.enumerate() − This method returns a list of all thread objects that are currently active.

How to create threads using the <threading> module?

在本节中,我们将学习如何使用 <threading> 模块创建线程。请按照以下步骤使用 <threading> 模块创建新线程 −

In this section, we will learn how to create threads using the <threading> module. Follow these steps to create a new thread using the <threading> module −

  1. Step 1 − In this step, we need to define a new subclass of the Thread class.

  2. Step 2 − Then for adding additional arguments, we need to override the init(self [,args]) method.

  3. Step 3 − In this step, we need to override the run(self [,args]) method to implement what the thread should do when started.

Example

请考虑此示例,以了解如何使用 <threading> 模块生成新线程。

Consider this example to learn how to generate a new thread by using the <threading> module.

import threading
import time
exitFlag = 0

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 ("Starting " + self.name)
      print_time(self.name, self.counter, 5)
      print ("Exiting " + self.name)
def print_time(threadName, delay, counter):
   while counter:
      if exitFlag:
         threadName.exit()
      time.sleep(delay)
      print ("%s: %s" % (threadName, time.ctime(time.time())))
      counter -= 1

thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("Exiting Main Thread")
Starting Thread-1
Starting Thread-2

Output

现在,请考虑以下输出:

Now, consider the following output −

Thread-1: Mon Apr 23 10:52:09 2018
Thread-1: Mon Apr 23 10:52:10 2018
Thread-2: Mon Apr 23 10:52:10 2018
Thread-1: Mon Apr 23 10:52:11 2018
Thread-1: Mon Apr 23 10:52:12 2018
Thread-2: Mon Apr 23 10:52:12 2018
Thread-1: Mon Apr 23 10:52:13 2018
Exiting Thread-1
Thread-2: Mon Apr 23 10:52:14 2018
Thread-2: Mon Apr 23 10:52:16 2018
Thread-2: Mon Apr 23 10:52:18 2018
Exiting Thread-2
Exiting Main Thread

Python Program for Various Thread States

有五种线程状态 - 新建、可运行、运行、等待和终止。在这五种中,我们主要关注三种状态 - 运行、等待和终止。一个线程在其运行状态获得其资源,在其等待状态等待资源;如果执行且已获取,资源在终止状态的最终释放。

There are five thread states - new, runnable, running, waiting and dead. Among these five Of these five, we will majorly focus on three states - running, waiting and dead. A thread gets its resources in the running state, waits for the resources in the waiting state; the final release of the resource, if executing and acquired is in the dead state.

以下 Python 程序借助 start()、sleep() 和 join() 方法将分别显示一个线程如何进入运行、等待和终止状态。

The following Python program with the help of start(), sleep() and join() methods will show how a thread entered in running, waiting and dead state respectively.

Step 1 - 导入必要的模块,<threading> 和 <time>

Step 1 − Import the necessary modules, <threading> and <time>

import threading
import time

Step 2 - 定义一个将在创建线程时调用的函数。

Step 2 − Define a function, which will be called while creating a thread.

def thread_states():
   print("Thread entered in running state")

Step 3 - 我们正在使用 time 模块的 sleep() 方法,让我们的线程等待 2 秒左右。

Step 3 − We are using the sleep() method of time module to make our thread waiting for say 2 seconds.

time.sleep(2)

Step 4 - 现在,我们创建了一个名为 T1 的线程,它采用上述定义函数的参数。

Step 4 − Now, we are creating a thread named T1, which takes the argument of the function defined above.

T1 = threading.Thread(target=thread_states)

Step 5 - 现在,借助 start() 函数,我们可以启动我们的线程。它将生成消息,该消息已在我们定义函数时设定。

Step 5 − Now, with the help of the start() function we can start our thread. It will produce the message, which has been set by us while defining the function.

T1.start()
Thread entered in running state

Step 6 - 现在,在最后,我们可以在线程完成其执行后使用 join() 方法终止它。

Step 6 − Now, at last we can kill the thread with the join() method after it finishes its execution.

T1.join()

Starting a thread in Python

在 Python 中,我们可以通过不同的方式启动一个新线程,但其中最简单的一种方法是将其定义为一个单独的函数。在定义函数后,我们可以将其作为新 threading.Thread 对象的目标来传递,以此类推。执行以下 Python 代码,以了解该函数的工作原理:

In python, we can start a new thread by different ways but the easiest one among them is to define it as a single function. After defining the function, we can pass this as the target for a new threading.Thread object and so on. Execute the following Python code to understand how the function works −

import threading
import time
import random
def Thread_execution(i):
   print("Execution of Thread {} started\n".format(i))
   sleepTime = random.randint(1,4)
   time.sleep(sleepTime)
   print("Execution of Thread {} finished".format(i))
for i in range(4):
   thread = threading.Thread(target=Thread_execution, args=(i,))
   thread.start()
   print("Active Threads:" , threading.enumerate())

Output

Execution of Thread 0 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>]

Execution of Thread 1 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>,
      <Thread(Thread-3577, started 3080)>]

Execution of Thread 2 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>,
      <Thread(Thread-3577, started 3080)>,
      <Thread(Thread-3578, started 2268)>]

Execution of Thread 3 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>,
      <Thread(Thread-3577, started 3080)>,
      <Thread(Thread-3578, started 2268)>,
      <Thread(Thread-3579, started 4520)>]
Execution of Thread 0 finished
Execution of Thread 1 finished
Execution of Thread 2 finished
Execution of Thread 3 finished

Daemon threads in Python

在 Python 中实现守护线程之前,我们需要了解守护线程及其用法。在计算术语中,守护程序是一个后台进程,它处理各种服务(如数据发送、文件传输等)的请求。如果它不再需要,它将保持不活动。也可以借助非守护线程来完成相同任务。然而,在这种情况下,主线程必须手动跟踪非守护线程。另一方面,如果我们使用守护线程,那么主线程可以完全忘记这一点,当主线程退出时它将被终止。关于守护线程的另一个重点是,我们只能选择对非重要任务使用它们,这些任务在未完成或中途被终止时不会影响我们。以下是在 Python 中实现守护线程:

Before implementing the daemon threads in Python, we need to know about daemon threads and their usage. In terms of computing, daemon is a background process that handles the requests for various services such as data sending, file transfers, etc. It would be dormant if it is not required any more. The same task can be done with the help of non-daemon threads also. However, in this case, the main thread has to keep track of the non-daemon threads manually. On the other hand, if we are using daemon threads then the main thread can completely forget about this and it will be killed when main thread exits. Another important point about daemon threads is that we can opt to use them only for non-essential tasks that would not affect us if it does not complete or gets killed in between. Following is the implementation of daemon threads in python −

import threading
import time

def nondaemonThread():
   print("starting my thread")
   time.sleep(8)
   print("ending my thread")
def daemonThread():
   while True:
   print("Hello")
   time.sleep(2)
if __name__ == '__main__':
   nondaemonThread = threading.Thread(target = nondaemonThread)
   daemonThread = threading.Thread(target = daemonThread)
   daemonThread.setDaemon(True)
   daemonThread.start()
   nondaemonThread.start()

在上述代码中,有两个函数,分别是 >nondaemonThread()>daemonThread() 。第一个函数打印其状态并睡 8 秒,而 deamonThread() 函数则无限期地每 2 秒打印一次 Hello。借助以下输出,我们可以了解非守护线程和守护线程之间的区别:

In the above code, there are two functions namely >nondaemonThread() and >daemonThread(). The first function prints its state and sleeps after 8 seconds while the the deamonThread() function prints Hello after every 2 seconds indefinitely. We can understand the difference between nondaemon and daemon threads with the help of following output −

Hello

starting my thread
Hello
Hello
Hello
Hello
ending my thread
Hello
Hello
Hello
Hello
Hello