【C++】什么是协程?深入理解协程的实现原理与应用

技术要求

为了运行代码示例,你必须准备以下内容:

  • 一个能够编译和执行C++20的基于Linux的系统(例如,Linux Mint 21)

  • GCC12.2编译器 - https://gcc.gnu.org/git/gcc.git gcc-source:使用-fcoroutines、-std=c++2a、-lpthread和-lrt标志

  • 对于某些示例,你也可以选择使用https://godbolt.org/。

介绍协程

一个进程就是一个程序的运行实例。它有自己的地址空间,除了通过共享内存,不与其他进程共享。线程存在于进程中,它们不能脱离进程存在,尽管在Linux中,进程和线程都被视为任务。它们以相同的方式被调度,并且在内核级别有相同的控制结构。尽管如此,线程被认为是轻量级的,因为程序的初始负载的较大开销由父进程承担。

但这并不是完整的情况。还有纤程和协程。如果说进程和线程是真正的并发并且在共享资源上并行工作,纤程就像线程,但不符合并发。虽然线程通常依赖于任务调度器的抢占式时间分片,纤程使用协作式多任务处理。也就是说,它们在执行过程中自己让出控制权,以运行另一个纤程。它们也被称为有栈协程。与此同时,C++中的协程被称为无栈协程,不由操作系统管理。换句话说,有栈协程可以在嵌套的栈帧中被挂起,而无栈协程只能通过顶级例程嵌套。

协程技术相当古老。C++最近才引入它,它对于网络编程、I/O操作、事件管理等非常有用。协程也被认为是具有暂停能力的执行。尽管如此,它们以协作方式提供多任务处理,并不并行工作。这意味着任务不能同时执行。同时,它们是实时友好的,允许在协程之间快速切换上下文,不需要系统调用。事实上,它们对硬实时操作系统友好,因为执行顺序和调度由系统程序员控制,正如你稍后将在本章中看到的。C++中的协程非常适用于实现任务图和状态机等。

你们可能想知道协程和标准单线程函数式编程之间的区别。嗯,后者被认为是同步方法,而前者是具有同步可读性的异步方法。但协程真正关注的是减少不必要的等待,并在准备所需资源或调用时做一些有用的事情。以下简图虽然简单,但提醒我们同步和异步执行之间的相应区别。

图1同步与异步应用程序执行

普通的单线程执行在某些方面也是有限的。首先,程序内部无法追踪调用、挂起或恢复函数,或者至少不能通过引用追踪。换句话说,控制流在后台发生且是隐式的。此外,控制流有一个严格的方向 - 函数要么返回到其调用者,要么继续向内调用另一个函数。每个函数调用在栈上创建一个新记录,并立即发生,一旦调用,方法不能被延迟。一旦该函数返回,其在栈上的部分就被清除,无法恢复。换言之,激活是无法追踪的。

另一方面,协程拥有自己的生命周期。协程是一个对象,可以明确地被引用。如果协程应该比其调用者存活更久,或者应该被转移给另一个对象,则可以将其存储在堆中。同时,控制权可以在协程之间双向传递 -向上或向下。协程增加了函数调用和函数类型的含义。int func(int arg)原型意味着一个名为func的函数,接收一个整数类型的参数arg,返回一个整数。类似的协程可能永远不会返回到其调用者,而调用者期望的值可能由另一个协程产生。让我们看看C++中是如何发生的。

C++中的协程设施

最初,你可以将它们视为智能指针。你已经知道它们是指针的包装器,并为内存管理提供额外的控制。协程以类似的方式工作,但围绕它们的代码更复杂。这次,我们需要一个函数原型的包装器。这个包装器将处理数据流和调度控制。包装器本身就是协程。我们定义了一个Task exCoroutine()任务(任务与Linux定义的任务不同) - 如果它使用以下三个操作符之一:co_await、co_yield或co_return,则被解释为协程。这里是一个例子:

#include <coroutine>
...
Task exCoroutine() {
    co_return;
}
int main() { Task async_task = exCoroutine(); } 

包装器类型当前是Task。它在调用者级别上是已知的。通过co_return操作符,协程对象被识别为exCoroutine()函数。创建Task类是系统程序员的工作。它不是标准库的一部分。那么Task类是什么?

struct Task {
    struct promise_type {
        Task get_return_object()
            { return {}; }
        std::suspend_never initial_suspend()
            { return {}; }
        std::suspend_never final_suspend() noexcept
            { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

重要提示

这是一个非常通用的模式,几乎在每个协程示例中都会使用。参考:https://en.cppreference.com/w/cpp/language/coroutines

我们称执行给定例程但不返回值的协程为任务。此外,协程与promise对象相关联 。promise对象在协程级别上被操纵。协程通过这个对象返回操作结果或引发异常。这个设施还需要协程帧(或协程状态),这是一个在堆上的内部对象,包含promise。它还由传递的参数组成 - 通过值复制,当前调用引用的表示;暂停点,以便协程相应地恢复;以及该点范围之外的局部变量。那么,我们的代码做了什么?嗯,从用户的角度来看,它什么也没做,但在后台发生了很多事情。让我们观察以下图表:

图2

### 结合YOLOv8PaddleOCR进行目标检测和文字识别 #### 数据准备 为了使YOLOv8能够有效地定位图像中的车牌位置,需先准备好标注好的训练集。这些数据应包含不同环境下的车辆图片以及对应的标签文件,用于指示每张图里车牌的具体坐标。 #### 模型配置 对于YOLOv8而言,在`yolov8.yaml`配置文档内指定输入尺寸、锚框参数等超参设置;而针对PaddleOCR,则无需额外调整其默认设定即可满足大多数场景需求[^3]。 #### 代码实现 下面给出一段Python脚本片段来展示怎样串联起这两个组件: ```python from ultralytics import YOLO from paddleocr import PaddleOCR def detect_and_recognize(image_path): # 加载YOLOv8模型并执行预测操作 model = YOLO('path/to/yolov8.pt') results = model.predict(source=image_path, conf=0.25) ocr = PaddleOCR(use_angle_cls=True, lang='en') final_result = [] for result in results: boxes = result.boxes.cpu().numpy() for box in boxes: cropped_image = crop_box_from_image(image_path, box) # 自定义函数裁剪区域 ocr_results = ocr.ocr(cropped_image, cls=True) recognized_text = ''.join([line[1][0] for line in ocr_results]) final_result.append({ 'bbox': list(map(int, box[:4])), 'text': recognized_text, 'confidence': float(box[-1]), }) return final_result ``` 此段程序首先利用YOLOv8完成对给定路径下图片的目标检测任务,随后依据所得边界框信息截取出可能含有字符的部分送入至PaddleOCR作进一步的文字解析工作,最终返回融合后的结构化输出结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值