YOLO 模型的线程安全推理#
要在 Python 线程中安全地运行 Ultralytics YOLO推理,请在每个线程中分别实例化一个 YOLO 模型,而不要让多个线程共享同一个实例。共享单个模型会引发竞态条件,破坏模型的内部状态并导致结果不可预测,因为 Python 的 threading 模块会让多个线程同时操作同一个对象。本指南将解释共享模型为何会失败,展示安全的线程独立模式,并介绍在必须共享实例时使用的 ThreadingLocked 装饰器。
跳转至共享模型为何会失败、线程安全模式或ThreadingLocked 装饰器。
观看: 如何在 Python 中使用 Ultralytics YOLO 模型执行线程安全推理 | 多线程 🚀
了解 Python 线程#
Python 线程是一种并行形式,可让程序同时执行多个操作。不过,Python 的全局解释器锁(GIL)意味着同一时间只能有一个线程执行 Python 字节码。
这听起来像是一种限制,但线程仍然可以提供并发能力,尤其适用于 I/O 密集型操作,或使用会释放 GIL 的操作时,例如 YOLO 底层 C 库执行的操作。
共享模型实例的风险#
在多个线程之外实例化 YOLO 模型并让这些线程共享该实例,可能会导致竞态条件:并发访问会不一致地修改模型内部状态。如果模型或其组件持有未设计为线程安全的状态,这个问题尤其严重。
非线程安全示例:单个模型实例#
在 Python 中使用线程时,了解哪些模式可能导致并发问题非常重要。以下是你应该避免的做法:让多个线程共享同一个 YOLO26 模型实例。
# 不安全:多个线程共享同一个模型实例
from threading import Thread
from ultralytics import YOLO
# 在线程外实例化模型
shared_model = YOLO("yolo26n.pt")
def predict(image_path):
"""Predicts objects in an image using a preloaded YOLO model, take path string to image as argument."""
results = shared_model.predict(image_path)
# 处理结果
# 启动共享同一模型实例的线程
Thread(target=predict, args=("image1.jpg",)).start()
Thread(target=predict, args=("image2.jpg",)).start()在上面的示例中,多个线程会使用 shared_model,这可能导致结果不可预测,因为多个线程可能同时执行 predict。
安全示例:每个线程使用独立实例#
只要每个线程都拥有自己的模型实例并且绝不与其他线程共享,使用多个独立的模型实例就没有问题。下面的实例在线程启动前创建也无妨——唯一不安全的模式是在多个线程之间共享同一个实例:
# 安全:每个线程使用自己的专用模型实例
from threading import Thread
from ultralytics import YOLO
# 每个线程实例化一个模型
model_1 = YOLO("yolo26n.pt")
model_2 = YOLO("yolo26n.pt")
def predict(model, image_path):
"""Runs prediction on an image using a specified YOLO model, returning the results."""
results = model.predict(image_path)
# 处理结果
# 每个线程使用独立的专用模型实例
Thread(target=predict, args=(model_1, "image1.jpg")).start()
Thread(target=predict, args=(model_2, "image2.jpg")).start()由于每个线程都使用自己的专用实例,因此不存在可被线程破坏的共享模型状态。如下所示,在每个线程内部实例化模型只是确保实例不会被意外共享的最简单方法。
线程安全推理#
要执行线程安全的推理,你应在每个线程内分别实例化一个 YOLO 模型。这样可以确保每个线程拥有独立的模型实例,从而消除竞态条件风险。
线程安全示例#
以下是在每个线程内部实例化 YOLO 模型以安全并行推理的方法:
# 安全:在每个线程内实例化一个模型
from threading import Thread
from ultralytics import YOLO
def thread_safe_predict(image_path):
"""Predict on an image using a new YOLO model instance in a thread-safe manner; takes image path as input."""
local_model = YOLO("yolo26n.pt")
results = local_model.predict(image_path)
# 处理结果
# 启动各自拥有模型实例的线程
Thread(target=thread_safe_predict, args=("image1.jpg",)).start()
Thread(target=thread_safe_predict, args=("image2.jpg",)).start()在此示例中,每个线程都会创建自己的 YOLO 实例。这样可防止任何线程干扰其他线程的模型状态,确保每个线程都能安全地执行推理,且不会与其他线程产生意外交互。
使用 ThreadingLocked 装饰器#
Ultralytics 提供了 ThreadingLocked 装饰器,可用于确保函数以线程安全的方式执行。该装饰器使用锁来保证同一时间只有一个线程能够执行被装饰的函数。
from ultralytics import YOLO
from ultralytics.utils import ThreadingLocked
# 创建模型实例
model = YOLO("yolo26n.pt")
# 为预测函数添加装饰器,使其具备线程安全性
@ThreadingLocked()
def thread_safe_predict(image_path):
"""Thread-safe prediction using a shared model instance."""
results = model.predict(image_path)
return results
# 现在,你可以从多个线程安全地调用此函数当你需要让多个线程共享一个模型实例,同时又要确保同一时间只有一个线程能够访问它时,ThreadingLocked 装饰器尤其有用。
与在每个线程中加载模型相比,共享一个加锁的模型实例可以节省内存,但会降低并发能力,因为线程会在锁上排队依次等待。内存充足且希望获得最大并行度时,优先采用每线程独立实例的模式;如果模型内存成为瓶颈,则可以使用 ThreadingLocked。
结论#
在 Python 的 threading 中使用 YOLO 模型时,为每个线程创建自己的专用模型实例,绝不要让多个线程共享同一个实例。在使用模型的线程内部实例化模型是确保这一点最简单的方法,可以避免竞态条件并确保推理任务可靠运行。
对于更高级的场景,如需进一步优化多线程推理性能,可以考虑使用基于进程的并行处理方式,例如 multiprocessing,或者借助任务队列和专用工作进程。
常见问题#
要在多线程 Python 环境中使用 Ultralytics YOLO 模型时避免竞态条件,请在每个线程内分别实例化一个 YOLO 模型。这样可确保每个线程拥有独立的模型实例,避免并发修改模型状态。
示例:
from threading import Thread from ultralytics import YOLO def thread_safe_predict(image_path): """Predict on an image in a thread-safe manner.""" local_model = YOLO("yolo26n.pt") results = local_model.predict(image_path) # 处理结果 Thread(target=thread_safe_predict, args=("image1.jpg",)).start() Thread(target=thread_safe_predict, args=("image2.jpg",)).start()如需了解确保线程安全的更多信息,请访问YOLO 模型的线程安全推理。
要在 Python 中安全地运行多线程 YOLO 模型推理,请遵循以下最佳实践:
- 在每个线程中分别实例化 YOLO 模型,不要让多个线程共享同一个模型实例。
- 使用 Python 的
multiprocessing模块进行并行处理,以避免与全局解释器锁(GIL)相关的问题。 - 请记住,YOLO 底层的 C 库(PyTorch、OpenCV)会在繁重计算期间自动释放 GIL,因此线程仍可并发运行推理。
- 如果内存有限,可以考虑在共享模型实例时使用
ThreadingLocked装饰器。
线程安全的模型实例化示例:
from threading import Thread from ultralytics import YOLO def thread_safe_predict(image_path): """Runs inference in a thread-safe manner with a new YOLO model instance.""" local_model = YOLO("yolo26n.pt") results = local_model.predict(image_path) # 处理结果 # 启动多个线程 Thread(target=thread_safe_predict, args=("image1.jpg",)).start() Thread(target=thread_safe_predict, args=("image2.jpg",)).start()如需更多背景信息,请参阅线程安全推理一节。
每个线程都应该拥有自己的 YOLO 模型实例,以避免竞态条件。当多个线程共享同一个模型实例时,并发访问可能导致行为不可预测,并修改模型的内部状态。使用独立实例可确保线程彼此隔离,让多线程任务可靠且安全。
如需详细指导,请查看非线程安全示例:单个模型实例和线程安全示例两节。
Python 的全局解释器锁(GIL)规定同一时间只能有一个线程执行 Python 字节码,这可能会限制 CPU 密集型多线程任务的性能。不过,对于 I/O 密集型操作,或使用会释放 GIL 的库的进程(例如 YOLO 底层的 C 库),仍然可以实现并发。要获得更高性能,可以考虑使用 Python 的
multiprocessing模块进行基于进程的并行处理。如需了解更多有关 Python 线程的信息,请参阅了解 Python 线程一节。
是的,使用 Python 的
multiprocessing模块并行运行 YOLO 模型推理更安全,而且通常效率更高。基于进程的并行处理会创建相互独立的内存空间,从而避开全局解释器锁(GIL)并降低并发问题的风险。每个进程都会独立运行,并拥有自己的 YOLO 模型实例。如需进一步了解 YOLO 模型的基于进程的并行处理,请参阅线程安全推理页面。