Ultralytics YOLO27:

使用 YOLO 模型进行线程安全推理#

要在 Python 线程之间安全地运行 Ultralytics YOLO 推理,请在每个线程内实例化一个独立的 YOLO 模型,而不是在多个线程之间共享同一个实例。共享单个模型会导致竞态条件,破坏其内部状态并产生不可预测的结果,因为 Python 的 threading 模块会让多个线程并发操作同一个对象。本指南将解释共享模型失败的原因,展示安全的每线程模式,并介绍在必须共享实例时使用的 ThreadingLocked 装饰器。

跳转到共享模型失败的原因线程安全模式ThreadingLocked 装饰器



Watch: How to Perform Thread Safe Inference with Ultralytics YOLO Models in Python | Multi-Threading 🚀

理解 Python 线程#

Python 线程是一种并行形式,可以让程序同时运行多个操作。但是,Python 的全局解释器锁 (GIL) 意味着同一时间只能有一个线程执行 Python 字节码。

Single-thread vs multi-thread inference

虽然这听起来像是一种限制,但线程仍然可以提供并发能力,尤其适用于 I/O 密集型操作,或使用会释放 GIL 的操作(例如 YOLO 底层 C 库执行的操作)时。

共享模型实例的风险#

在你的线程外实例化 YOLO 模型,并在多个线程之间共享该实例,可能导致竞态条件:由于并发访问,模型的内部状态会被不一致地修改。当模型或其组件持有未针对线程安全设计的状态时,这个问题尤其严重。

非线程安全示例:单个模型实例#

在 Python 中使用线程时,务必识别可能导致并发问题的模式。以下是应避免的做法:在多个线程之间共享单个 YOLO26 模型实例。

# Unsafe: Sharing a single model instance across threads
from threading import Thread

from ultralytics import YOLO

# Instantiate the model outside the thread
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)
    # Process results

# Starting threads that share the same model instance
Thread(target=predict, args=("image1.jpg",)).start()
Thread(target=predict, args=("image2.jpg",)).start()

在上面的示例中,多个线程使用 shared_model,这可能导致不可预测的结果,因为多个线程可能会同时执行 predict

安全示例:每个线程使用专用实例#

只要每个线程拥有自己的实例,且绝不与其他线程共享,使用多个独立的模型实例就是安全的。这些实例在各线程启动前创建并不重要——唯一不安全的模式是在多个线程之间共享一个实例:

# Safe: each thread uses its own dedicated model instance
from threading import Thread

from ultralytics import YOLO

# Instantiate one model per thread
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)
    # Process results

# Each thread uses a separate, dedicated model instance
Thread(target=predict, args=(model_1, "image1.jpg")).start()
Thread(target=predict, args=(model_2, "image2.jpg")).start()

由于每个线程都使用自己的专用实例,因此不存在可供线程破坏的共享模型状态。如下一节所示,在每个线程内实例化模型,只是确保实例不会被意外共享的最简单方法。

线程安全推理#

要执行线程安全的推理,你应在每个线程内实例化一个独立的 YOLO 模型。这样可以确保每个线程都拥有彼此隔离的模型实例,从而消除竞态条件风险。

线程安全示例#

下面介绍如何在每个线程内实例化 YOLO 模型,以安全地进行并行推理:

# Safe: Instantiating a single model inside each thread
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)
    # Process results

# Starting threads that each have their own model instance
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

# Create a model instance
model = YOLO("yolo26n.pt")

# Decorate the prediction function to make it thread-safe
@ThreadingLocked()
def thread_safe_predict(image_path):
    """Thread-safe prediction using a shared model instance."""
    results = model.predict(image_path)
    return results

# Now you can safely call this function from multiple threads

当你需要在多个线程之间共享模型实例,但又希望确保同一时间只有一个线程可以访问它时,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)
        # Process results
    
    Thread(target=thread_safe_predict, args=("image1.jpg",)).start()
    Thread(target=thread_safe_predict, args=("image2.jpg",)).start()

    如需了解确保线程安全的更多信息,请访问 使用 YOLO 模型进行线程安全推理

  • 要在 Python 中安全地运行多线程 YOLO 模型推理,请遵循以下最佳实践:

    1. 在每个线程内实例化 YOLO 模型,而不是在多个线程之间共享单个模型实例。
    2. 使用 Python 的 multiprocessing 模块进行并行处理,以避免与全局解释器锁 (GIL) 相关的问题。
    3. 请记住,YOLO 的底层 C 库(PyTorch、OpenCV)会在执行繁重计算期间自动释放 GIL,因此线程仍然可以并发运行推理。
    4. 当内存受限时,可以考虑对共享模型实例使用 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)
        # Process results
    
    # Initiate multiple threads
    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 模型进行基于进程的并行处理,请参阅线程安全推理页面。

评论