Python 线程:简介
已发表: 2022-10-25在本教程中,您将学习如何使用 Python 的内置线程模块来探索 Python 中的多线程功能。
从进程和线程的基础知识开始,您将了解多线程在 Python 中的工作原理,同时了解并发和并行的概念。 然后,您将学习如何使用内置的threading
模块在 Python 中启动和运行一个或多个线程。
让我们开始吧。
进程与线程:有什么区别?
什么是过程?
进程是需要运行的程序的任何实例。
它可以是任何东西——Python 脚本或网络浏览器(如 Chrome)到视频会议应用程序。 如果您在计算机上启动任务管理器并导航到Performance –> CPU ,您将能够看到当前在您的 CPU 内核上运行的进程和线程。

了解进程和线程
在内部,一个进程有一个专用的内存来存储与该进程相对应的代码和数据。
一个进程由一个或多个线程组成。 线程是操作系统可以执行的最小指令序列,它代表了执行的流程。
每个线程都有自己的堆栈和寄存器,但没有专用内存。 与进程关联的所有线程都可以访问数据。 因此,数据和内存由进程的所有线程共享。

在具有 N 个内核的 CPU 中,N 个进程可以在同一时间实例并行执行。 但是,同一进程的两个线程永远不能并行执行,但可以同时执行。 我们将在下一节讨论并发与并行的概念。
根据我们目前所学的知识,让我们总结一下进程和线程之间的区别。
特征 | 过程 | 线 |
记忆 | 专用内存 | 共享内存 |
执行方式 | 并行,并发 | 同时; 但不平行 |
执行由 | 操作系统 | CPython 解释器 |
Python中的多线程
在 Python 中,全局解释器锁 (GIL) 确保只有一个线程可以获取锁并在任何时间点运行。 所有线程都应该获得这个锁才能运行。 这确保了在任何给定时间点只能执行单个线程,并避免同时执行多线程。
例如,考虑同一进程的两个线程t1
和t2
。 因为线程在t1
读取特定值k
时共享相同的数据,所以t2
可以修改相同的值k
。 这可能导致死锁和不良结果。 但是只有一个线程可以获取锁并在任何实例上运行。 因此,GIL 也保证了线程安全。
那么我们如何在 Python 中实现多线程能力呢? 为了理解这一点,让我们讨论并发和并行的概念。
并发与并行:概述
考虑一个具有多个内核的 CPU。 在下图中,CPU 有四个内核。 这意味着我们可以在任何给定时刻并行运行四种不同的操作。
如果有四个进程,那么每个进程都可以在四个核中的每一个上独立并同时运行。 假设每个进程有两个线程。

要了解线程的工作原理,让我们从多核处理器架构切换到单核处理器架构。 如前所述,在特定的执行实例中只能有一个线程处于活动状态; 但处理器内核可以在线程之间切换。

例如,I/O 绑定线程经常等待 I/O 操作:读入用户输入、数据库读取和文件操作。 在这段等待时间内,它可以释放锁,以便其他线程可以运行。 等待时间也可以是简单的操作,比如休眠n
秒。
总结:在等待操作期间,线程释放锁,使处理器内核能够切换到另一个线程。 等待期结束后,较早的线程恢复执行。 这个过程,其中处理器内核同时在线程之间切换,促进了多线程。
如果您想在应用程序中实现进程级并行性,请考虑使用多处理。
Python 线程模块:第一步
Python 附带一个threading
模块,您可以将其导入 Python 脚本。
import threading
要在 Python 中创建线程对象,可以使用Thread
构造函数: threading.Thread(...)
。 这是足以满足大多数线程实现的通用语法:
threading.Thread(target=...,args=...)
这里,
-
target
是表示 Python 可调用对象的关键字参数 args
是目标接受的参数元组。
您将需要 Python 3.x 来运行本教程中的代码示例。 下载代码并跟随。
如何在 Python 中定义和运行线程
让我们定义一个运行目标函数的线程。
目标函数是some_func
。
import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() print(threading.active_count())
让我们解析一下上面的代码片段做了什么:
- 它导入
threading
和time
模块。 - 函数
some_func
具有描述性的print()
语句,并包含一个两秒的休眠操作:time.sleep(n)
使函数休眠n
秒。 - 接下来,我们定义一个线程
thread_1
,目标为some_func
。threading.Thread(target=...)
创建一个线程对象。 - 注意:指定函数的名称而不是函数调用; 使用
some_func
而不是some_func()
。 - 创建线程对象不会启动线程; 在线程对象上调用
start()
方法就可以了。 - 要获取活动线程的数量,我们使用
active_count()
函数。
Python 脚本在主线程上运行,我们正在创建另一个线程 ( thread1
) 来运行函数some_func
,因此活动线程数为两个,如输出所示:

# Output Running some_func... 2 Finished running some_func.
如果我们仔细查看输出,我们会看到在启动thread1
时,第一个 print 语句运行。 但在睡眠操作期间,处理器切换到主线程并打印出活动线程的数量——无需等待thread1
1 完成执行。

等待线程完成执行
如果你想让thread1
完成执行,你可以在启动线程后调用它的join()
方法。 这样做将等待thread1
完成执行而不切换到主线程。
import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() thread1.join() print(threading.active_count())
现在, thread1
在我们打印出活动线程数之前已经完成了执行。 所以只有主线程在运行,这意味着活动线程数为1。
# Output Running some_func... Finished running some_func. 1
如何在 Python 中运行多个线程
接下来,让我们创建两个线程来运行两个不同的功能。
这里, count_down
是一个函数,它接受一个数字作为参数,并从该数字倒数到零。
def count_down(n): for i in range(n,-1,-1): print(i)
我们定义count_up
,这是另一个从零计数到给定数字的 Python 函数。
def count_up(n): for i in range(n+1): print(i)
当使用具有语法
range(start, stop, step)
的range()
函数时,默认情况下会排除终点stop
。– 要从特定数字倒数到零,您可以使用负
step
值 -1 并将stop
值设置为 -1,以便包括零。– 同样,要计数到
n
,您必须将stop
值设置为n + 1
。 因为start
和step
的默认值分别为 0 和 1,所以您可以使用range(n + 1)
来获取从 0 到 n 的序列。
接下来,我们定义两个线程thread1
和 thread2 来分别运行count_down
和thread2
count_up
。 我们为这两个函数添加了print
语句和sleep
操作。
创建线程对象时,请注意目标函数的参数应指定为元组 - 到args
参数。 由于这两个函数( count_down
和count_up
)都接受一个参数。 您必须在值之后显式插入逗号。 这确保了参数仍然作为元组传入,因为后续元素被推断为None
。
import threading import time def count_down(n): for i in range(n,-1,-1): print("Running thread1....") print(i) time.sleep(1) def count_up(n): for i in range(n+1): print("Running thread2...") print(i) time.sleep(1) thread1 = threading.Thread(target=count_down,args=(10,)) thread2 = threading.Thread(target=count_up,args=(5,)) thread1.start() thread2.start()
在输出中:
- 函数
count_up
在thread2
上运行,从 0 开始最多计数 5。 -
count_down
函数在thread1
上运行,从 10 倒数到 0。
# Output Running thread1.... 10 Running thread2... 0 Running thread1.... 9 Running thread2... 1 Running thread1.... 8 Running thread2... 2 Running thread1.... 7 Running thread2... 3 Running thread1.... 6 Running thread2... 4 Running thread1.... 5 Running thread2... 5 Running thread1.... 4 Running thread1.... 3 Running thread1.... 2 Running thread1.... 1 Running thread1.... 0
您可以看到thread1
和thread2
交替执行,因为它们都涉及等待操作(睡眠)。 一旦count_up
函数完成计数到 5, thread2
就不再处于活动状态。 所以我们得到的输出只对应于thread1
。
加起来
在本教程中,您学习了如何使用 Python 的内置线程模块来实现多线程。 以下是关键要点的摘要:
- Thread构造函数可用于创建线程对象。 使用threading.Thread(target=<callable>,args=(<tuple of args>))创建一个线程,该线程使用args中指定的参数运行目标可调用对象。
- Python 程序在主线程上运行,因此您创建的线程对象是附加线程。 您可以调用active_count()函数返回任何实例的活动线程数。
- 您可以使用线程对象上的start()方法启动一个线程,然后使用join()方法等待它完成执行。
您可以通过调整等待时间、尝试不同的 I/O 操作等来编写更多示例。 确保在即将到来的 Python 项目中实现多线程。 快乐编码!