知微集:Python中的线程Thread(一)
欢迎来到"一起学点什么吧"的合集「NLP知微集」。在这里,我们不愿宏大叙事,只聚焦于自然语言处理领域中那些细微却关键的“齿轮”与“螺丝钉”。我相信,真正深刻的理解,源于对细节的洞察。本期,我将为您拆解的是:[Python线程]。
关于Python线程的相关内容,比较多,准备分几期来一起探讨一下Python线程的问题。
Python线程
在Python中线程是什么样的?
线程是指计算机程序中的一个执行流。
每个程序都是一个进程,并且至少有一个线程来执行该进程的指令。
当我们运行 Python 脚本时,它启动 Python 解释器的实例,在主线程中运行我们的代码。主线程是 Python 进程的默认线程。
我们可以开发程序以并发执行任务,在这种情况下,我们可能需要创建和运行新的线程。这些将是程序之外的并发执行线程,例如:
- 并发执行函数调用。
- 并发执行对象方法。
Python 线程是底层操作系统提供的原生线程的对象表示。
当我们创建并运行一个新线程时,Python 将对底层操作系统进行系统调用,请求创建一个新线程并启动新线程的运行。
这表明 Python 线程是真正的线程,而不是模拟的软件线程。
在新线程中的代码可能会也可能不会并行执行(同时执行),尽管这些线程是并发执行的。造成这种现象的原因有很多,例如:
- 底层硬件可能支持或不支持并行执行(例如,单个 CPU 核心与多个 CPU 核心)。
- Python 解释器可能允许或不允许多个线程并行执行。
这突出了可以顺序执行(并发)的代码与能够同时执行(并行)的能力之间的区别。
- 并发(Concurrent):可以交错执行的代码。
- 并行(Parallel):同时执行代码的能力。
线程 vs 进程
一个进程指的是一个计算机程序。
每个进程实际上都是 Python 解释器的一个实例,该实例执行 Python 指令(Python 字节码),这比你在 Python 程序中输入的代码要低一个层次。
底层操作系统控制新进程的创建方式。在某些系统上,这可能需要创建一个新进程,而在其他系统上,这可能需要进程进行分叉。在 Python 中用于创建新进程的操作系统特定方法不是我们需要担心的事情,因为它由您安装的 Python 解释器管理。
线程始终存在于进程中,并代表指令或代码的执行方式。
Python 进程将在所有(非后台线程)终止后终止。
- 进程(Process):Python 解释器的实例至少有一个名为 MainThread 的线程。
- 线程(Thread):Python 进程中的一条执行线程,例如主线程或新创建的线程。
线程生命周期(Life-Cycle of a thread)
Python 中的线程表示为 threading.Thread 类的实例。
一旦线程启动,Python 解释器将与底层操作系统交互,请求创建一个新的原生线程。然后,threading.Thread 实例为该底层原生线程提供一个基于 Python 的引用。
每个线程都遵循相同的生命周期。理解这个生命周期的各个阶段,有助于在 Python 中开始并发编程。
一个 Python 线程可能经历其生命周期中的三个步骤:新线程、运行中的线程和已终止的线程。
在线程中运行函数
Python 函数可以使用 threading.Thread 类在单独的线程中执行。
如何在线程中运行函数
- 创建 threading.Thread 类的实例。
- 通过“target”参数指定函数的名称。
- 调用 start() 函数。
在另一个线程中执行的函数可能有参数,在这种情况下,可以将它们指定为一个元组,并传递给 threading.Thread 类构造函数的“args”参数,或者将它们作为一个字典传递给“kwargs”参数。
start()函数将立即返回,操作系统将在其能够执行时立即在单独的线程中执行该函数。
我们无法精确控制线程何时执行或由哪个 CPU 核心执行。这两项都是底层操作系统处理的职责。
在一个线程中运行函数的示例
# 在另一个线程中运行函数的示例
from time import sleep
from threading import Thread# 一个自定义函数,会阻塞一段时间
def task():# 阻塞一段时间sleep(1)# 显示消息print('This is from another thread')# 创建一个线程
thread = Thread(target=task)
# 运行线程
thread.start()
# 等待线程完成
print('Waiting for the thread...')
thread.join()
# 可以通过调用 join() 函数显式地等待新线程执行完成。这并非必需的,因为主线程不会在新线程完成之前退出。# Waiting for the thread...
# This is from another thread
在线程中运行带参数的函数示例
# 示例:在另一个线程中运行带参数的函数
from time import sleep
from threading import Thread# 一个自定义函数,会阻塞一段时间
def task(sleep_time, message):# 阻塞一段时间sleep(sleep_time)# 显示消息print(message)# 创建一个线程
thread = Thread(target=task, args=(1.5, 'New message from another thread'))
# 启动线程
thread.start()
# 等待线程完成
print('Waiting for the thread...')
thread.join()# Waiting for the thread...
# New message from another thread
扩展 Thread 类
可以通过扩展 threading.Thread 类并重写 run()函数来在另一个线程中执行函数。
如何扩展 Thread 类
可以通过扩展 threading.Thread 类来在另一个线程中运行代码。
# custom thread class
class CustomThread(Thread):# ...# 重写 threading.Thread 类的 run()函数,以包含希望在另一个线程中执行的代码。# override the run functiondef run(self):# ...
由于这是一个自定义类,您可以为其定义一个构造函数,并使用它来传入 run() 函数中可能需要的数据,并将其存储为实例变量(属性)。
可以在类上定义其他函数,以便将您可能需要在另一个线程中完成的工作分开处理。
最后,属性也可以用来存储在其他线程中执行的任何计算或 IO 的结果,这些结果可能需要在之后检索。
扩展线程类的示例
创建一个 CustomThread 类的实例,并调用 start()函数以在另一个线程中开始执行我们的 run()函数。在内部,start()函数将调用 run()函数。
# 示例:继承Thread类
from time import sleep
from threading import Thread# 自定义线程类
class CustomThread(Thread):# 重写run函数def run(self):# 阻塞一段时间sleep(1)# 显示消息print('This is coming from another thread')# 创建线程
thread = CustomThread()
# 启动线程
thread.start()
# 等待线程完成
print('Waiting for the thread to finish')
thread.join()
扩展线程类并返回值的示例
我们可能需要从线程中检索数据,例如返回值。
run() 函数没有办法将值返回给 start() 函数再返回给调用者。
相反,我们可以通过将它们存储为实例变量,并让调用者从这些实例变量中检索数据,从而从我们的 run() 函数中返回值。
可以更新 run() 函数以将某些数据存储为实例变量(也称为 Python 属性)。
# 示例:继承Thread类并返回值
from time import sleep
from threading import Thread# 自定义线程类
class CustomThread(Thread):# 重写run函数def run(self):# 阻塞一段时间sleep(1)# 显示消息print('This is coming from another thread')# 存储返回值self.value = 99# 创建线程
thread = CustomThread()
# 启动线程
thread.start()
# 等待线程完成
print('Waiting for the thread to finish')
thread.join()
# 获取从run返回的值
value = thread.value
print(f'Got: {value}')
返回多个值
# 示例:从线程返回多个值
from time import sleep
from threading import Thread# 自定义线程
class CustomThread(Thread):# 构造函数def __init__(self):# 执行基类构造函数Thread.__init__(self)# 设置默认值self.value1 = Noneself.value2 = Noneself.value3 = None# 在新线程中执行的函数def run(self):# 阻塞一段时间sleep(1)# 在实例变量中存储数据self.value1 = 'Hello from a new thread'self.value2 = 99self.value3 = False# 创建新线程
thread = CustomThread()
# 启动线程
thread.start()
# 等待线程完成
thread.join()
# 报告从线程返回的所有值
print(thread.value1)
print(thread.value2)
print(thread.value3)
全局变量返回值
# 示例:从线程返回一个值
from time import sleep
from threading import Thread# 在新线程中执行的函数
def task():# 阻塞一段时间sleep(1)# 正确作用域全局变量global data# 在全局变量中存储数据data = 'Hello from a new thread'# 定义全局变量
data = None
# 创建新线程
thread = Thread(target=task)
# 启动线程
thread.start()
# 等待线程完成
thread.join()
# 报告全局变量
print(data)
新线程随后将数据存储在全局变量中,确保显式指定作用域,以避免在阅读代码时产生混淆以及未来可能出现的错误。
主线程在新的线程终止之前阻塞,然后报告存储在全局变量中的模拟返回值。
Python线程属性图
结语
“见微知著,积跬步以至千里”,至此,关于 [Python线程] 的微探索之旅才缓缓拉开序幕,后续将一起详细去探索一下Python的线程相关问题。感谢您的耐心阅读。希望这片小小的“知微”碎片,能让你对Python线程有更清晰的认识。
点赞关注不迷路,点击合集标签「#NLP知微集」,不错过每一次细微的洞察。
下期再见,继续我们的拆解之旅!
Reference
- https://superfastpython.com/threading-in-python/