流水线是在"高级同步原语"中引入的一种机制,用于阶段性地管理工作,并协调多缓冲区(Multi-buffer)的生产者-消费者模式。它通常被用来实现计算与异步数据拷贝的重叠(Overlap) 。 本节详细介绍了主要通过 cuda::pipeline API 使用流水线的方法。
初始化 (Initialization)
cuda::pipeline 可以在不同的线程作用域(Scope)下创建。
-
对于非线程私有(
thread_scope_thread)的作用域,需要一个cuda::pipeline_shared_state<scope, count>对象来协调参与的线程。 -
这种"共享状态"封装了有限的资源,允许流水线同时处理最多
count个并发阶段(Stages)。
流水线分为统一型(Unified)或分区型(Partitioned):
-
统一型流水线:所有参与线程既是生产者又是消费者。
-
分区型流水线:每个线程的角色在流水线生命周期内固定,要么是生产者,要么是消费者。
-
注意 :线程私有流水线不能分区。创建分区流水线时,需向
cuda::make_pipeline()提供生产者数量或当前线程的角色。
为了支持分区,共享类型的 cuda::pipeline 会产生额外开销(例如每阶段使用一组共享内存屏障进行同步)。即使在可以使用 __syncthreads() 的统一型流水线中,这些开销依然存在。因此,在可能的情况下,优先使用线程私有流水线以规避开销。
如果说 cuda::barrier 是一个灵活的"信号灯",那么 cuda::pipeline 就是一整套自动化的生产线管理系统。
-
Stage(阶段)的概念 : 在高性能计算中,我们希望在 计算当前批次数据 (N) 的同时,硬件已经在异步搬运 下一批次 (N+1) 甚至 下下批次 (N+2) 的数据。
-
双缓冲 (Double Buffering) :
stages_count = 2。一个阶段在被消费者计算,另一个阶段在被生产者填充。 -
三缓冲 (Triple Buffering) :
stages_count = 3。这种设置能更激进地掩盖长距离访存延迟,防止计算单元因为等数据而停顿(Stall)。
-
-
角色分配(Unified vs Partitioned):
-
统一型(Unified):相当于"全员搬运,全员加工"。大家一起搬一段,搬完一起算。
-
分区型(Partitioned) :这就是我们前面展示的 Warp 特化。明确谁是专门跑腿的(Producer),谁是专门干活的(Consumer)。
-
-
Shared State(共享状态) : 这是 Pipeline 的核心"账本",通常存在
__shared__内存里。它记录了当前有多少个 Stage 正在搬运,多少个已经就绪。
线程私有流水线 (单线程异步)
cpp
// 作用域仅限于当前线程
constexpr auto scope = cuda::thread_scope_thread;
// 创建流水线实例,无需共享状态
cuda::pipeline<scope> pipeline = cuda::make_pipeline();
统一型流水线 (全块参与)
适用于整个 Thread Block 一起搬运,一起计算。
cpp
constexpr auto scope = cuda::thread_scope_block;
constexpr auto stages_count = 2; // 双缓冲
// 在共享内存中定义流水线的"账本"
__shared__ cuda::pipeline_shared_state<scope, stages_count> shared_state;
auto group = cooperative_groups::this_thread_block();
// 所有线程都参与,且既搬运也计算
auto pipeline = cuda::make_pipeline(group, &shared_state);
分区型流水线 (Warp 特化专用)
这就是实现生产者-消费者模式最优雅的代码写法。
cpp
constexpr auto scope = cuda::thread_scope_block;
constexpr auto stages_count = 2;
__shared__ cuda::pipeline_shared_state<scope, stages_count> shared_state;
auto group = cooperative_groups::this_thread_block();
// 角色划分:0号线程是生产者,其他是消费者
// 在实际项目中,通常是 thread_rank() < 32 为生产者
auto thread_role = (group.thread_rank() == 0) ?
cuda::pipeline_role::producer :
cuda::pipeline_role::consumer;
// 创建流水线,并绑定角色
auto pipeline = cuda::make_pipeline(group, &shared_state, thread_role);
你可能会问:"上一节刚才手写 4 个屏障不是也能实现双缓冲吗?"
-
管理复杂度 :当 Stage 数量增加到 3 个甚至更多(多缓冲)时,手写 Barrier 的状态转移逻辑会极其复杂且易错。Pipeline 封装了这些
acquire/release逻辑。 -
资源抽象 :
pipeline_shared_state自动处理了底层硬件资源的分配。 -
语义清晰 :代码中直接体现
producer和consumer角色,可读性极强,且编译器能针对特定的角色进行指令优化。
pipeline_shared_state
cpp
template <thread_scope _Scope, uint8_t _Stages_count>
class pipeline_shared_state;
指定作用域和并发阶段
pipeline_role
cpp
enum class pipeline_role
{
producer,
consumer
};
角色分为生产者和消费者
make_pipeline
它类似于工厂函数,创建pipeline对象。
cpp
friend _LIBCUDACXX_INLINE_VISIBILITY inline pipeline<thread_scope_thread> make_pipeline();
template <class _Group, thread_scope _Pipeline_scope, uint8_t _Pipeline_stages_count>
friend _LIBCUDACXX_INLINE_VISIBILITY pipeline<_Pipeline_scope>
make_pipeline(const _Group& __group, pipeline_shared_state<_Pipeline_scope, _Pipeline_stages_count>* __shared_state);
template <class _Group, thread_scope _Pipeline_scope, uint8_t _Pipeline_stages_count>
friend _LIBCUDACXX_INLINE_VISIBILITY pipeline<_Pipeline_scope>
make_pipeline(const _Group& __group,
pipeline_shared_state<_Pipeline_scope, _Pipeline_stages_count>* __shared_state,
size_t __producer_count);
template <class _Group, thread_scope _Pipeline_scope, uint8_t _Pipeline_stages_count>
friend _LIBCUDACXX_INLINE_VISIBILITY pipeline<_Pipeline_scope>
make_pipeline(const _Group& __group,
pipeline_shared_state<_Pipeline_scope, _Pipeline_stages_count>* __shared_state,
pipeline_role __role);
有四种make_pipeline的创建方式。和上面介绍的基本一致。
就是多了一个可以传递size_t __producer_count的版本。
Pipeline 会按照线程在当前 Group(通常是 Thread Block)中的 Rank(排名) 自动划分:
-
Rank < producer_count :自动被赋予
producer角色。 -
Rank >= producer_count :自动被赋予
consumer角色。
如果传递的是pipeline_role,系统会自动统计不同身份角色的个数。
提交任务 (Submitting Work)
将任务提交到流水线阶段(Stage)涉及以下步骤:
-
获取阶段(Acquire) :由一组生产者线程通过调用
pipeline.producer_acquire()集合性地获取流水线的"头部"(Head)。 -
提交操作(Submit) :向流水线头部提交异步操作(例如使用
memcpy_async搬运数据)。 -
提交阶段(Commit) :通过调用
pipeline.producer_commit()集合性地提交(并推进)流水线头部。
阻塞逻辑 :如果流水线的所有资源(即所有 Stage)都在使用中,pipeline.producer_acquire() 会阻塞生产者线程,直到消费者线程释放下一个流水线阶段的资源。
消耗任务 (Consuming Work)
从之前已提交(Committed)的阶段中消耗任务涉及以下步骤:
-
等待完成(Wait) :由一组消费者线程通过调用
pipeline.consumer_wait()集合性地等待阶段完成。该操作通常等待的是流水线的"尾部"(Tail),即最早提交但尚未处理的阶段。 -
释放阶段(Release) :通过调用
pipeline.consumer_release()集合性地释放该阶段。
对于线程私有作用域的流水线(cuda::pipeline<cuda::thread_scope_thread>),还可以使用 cuda::pipeline_consumer_wait_prior<N>() 友元函数。它允许线程等待除最后 N 个阶段以外的所有阶段完成,这与原语 API 中的 __pipeline_wait_prior(N) 功能类似。
Warp 缠绕 (Warp Entanglement)
在 GPU 硬件层面上,异步拷贝并不是"发出去就不管了"。硬件必须有一个地方记录:"当前有几批货正在路上?每一批分别运到了哪里?"
这个记录数据的地方叫做 硬件追踪槽位(Hardware Tracking Slots)。
-
物理实现 :这些槽位是实打实的硬件寄存器(寄存器资源极其昂贵)。当你定义
stages_count = 2时,硬件就会分配相应数量的槽位来追踪这两批数据的状态。 -
状态更新 :状态确实是在更新(比如从"传输中"变为"已完成"),但槽位编号是固定的。
-
资源占满 :如果槽位只有 2 个,而你发出了第 3 个提交指令(
commit),硬件就没地方记录这第 3 批货的进度了。为了保证数据安全,硬件只能强制让第 3 个提交指令挂起(Stall) ,直到前两个槽位中的一个被release。
为什么 producer_commit 会在全员到齐前就消耗 Stage?
这是最容易产生误解的地方。我们要区分 "软件层面的集体契约" 和 "硬件层面的指令发射"。
-
软件层面的契约(Collective) :
cuda::pipeline的 API 要求producer_count个人到齐。这是一种逻辑保障,确保 64 个人都干完活了再通知消费者。 -
硬件层面的行为(Instruction Issue) : 硬件(GPU)是按照 Warp 为单位执行指令的。它并不真正理解你在软件里设定的"64 人契约"。
- 只要有一个线程发出了
cp.async.commit_group指令,硬件就会立刻反应:"有人提交了一批任务,我得给它分配一个 追踪槽位(Stage)。"
- 只要有一个线程发出了
这就是问题的根源: 如果 Warp 内发生了分歧,32 个线程分 32 次发射了 commit 指令,即使你的逻辑目标是"大家一起搬一波数据",但硬件会诚实地为每一次指令发射分配一个独立的追踪槽位。
假设你设定了 stages_count = 2,而你的 Warp 发生了分歧:
-
线程 0 发射
commit:硬件占用 槽位 0 (Stage 0)。 -
线程 1 发射
commit:硬件占用 槽位 1 (Stage 1)。 -
线程 2 发射
commit:硬件发现没有空闲槽位了!-
此时,线程 2 会在硬件层面上被阻塞(Stall)。
-
死锁循环开启:
-
线程 2 没法完成
commit,导致整个 Warp 无法完成软件设定的"64 人签到"。 -
因为 64 人没签完,
consumer_wait()永远不会放行。 -
消费者不放行,就不会执行
consumer_release()。 -
不执行
release,硬件槽位 0 和 1 就永远不会释放。 -
槽位不释放,线程 2 就永远卡在
commit那里。
-
-
提前退出 (Early Exit)
当参与流水线的线程必须提前退出时,该线程在退出前必须使用 cuda::pipeline::quit() 显式地退出参与状态。这样,剩下的参与线程才能正常进行后续的流水线操作。
正如我们之前讨论的,cuda::pipeline 是一个 集体性(Collective) 的原语。这意味着:
-
计数器依赖 :流水线内部维护了参与线程的总数。每一次
producer_acquire或consumer_wait都期望收到全员的"信号"。 -
死锁风险 :如果一个线程在循环中途直接
return或跳出,流水线会认为这个线程"还在路上",从而导致其他线程在下一次同步点(如commit或wait)时永久等待这个失踪的线程。 -
动态调整 :调用
quit()相当于正式向流水线递交"辞职信"。流水线接收到后,会自动将"预期的参与线程总数"减去对应的数量,从而让剩下的线程能够继续完成剩余的 Stage。
追踪异步内存操作
以下示例演示了如何使用流水线结合异步内存拷贝,将数据从全局内存集体拷贝到共享内存,并使用流水线追踪拷贝操作。在这个例子中,每个线程使用自己的流水线独立提交内存拷贝,然后等待拷贝完成并消耗数据。
代码连续提交了三个阶段(Stage 1, Stage 2, Stage 3)。
-
Stage 1: 搬运第 1 个 block。
-
Stage 2: 搬运第 2 和第 3 个 block(演示了一个 Stage 可以包含多次拷贝)。
-
Stage 3: 搬运第 4 个 block。
cpp
#include <cuda/pipeline>
__global__ void example_kernel(const float *in) {
constexpr int block_size = 128;
// 共享内存总大小为 4 个 block
__shared__ __align__(sizeof(float)) float buffer[4 * block_size];
// 创建线程私有流水线
// 每个线程都有一个独立的"账本"来记录自己的搬运进度
cuda::pipeline<cuda::thread_scope_thread> pipeline = cuda::make_pipeline();
// --- 第一阶段:提交 ---
pipeline.producer_acquire();
// 每个线程负责搬运自己索引位置的一个 float
cuda::memcpy_async(buffer + threadIdx.x, in + threadIdx.x, sizeof(float), pipeline);
pipeline.producer_commit(); // 此时第一批货"上路了"
// --- 第二阶段:提交 ---
pipeline.producer_acquire();
// 一个 Stage 可以包含多个异步请求
cuda::memcpy_async(buffer + block_size + threadIdx.x, in + block_size + threadIdx.x, sizeof(float), pipeline);
cuda::memcpy_async(buffer + 2 * block_size + threadIdx.x, in + 2 * block_size + threadIdx.x, sizeof(float), pipeline);
pipeline.producer_commit();
// --- 第三阶段:提交 ---
pipeline.producer_acquire();
cuda::memcpy_async(buffer + 3 * block_size + threadIdx.x, in + 3 * block_size + threadIdx.x, sizeof(float), pipeline);
pipeline.producer_commit();
// --- 消耗阶段 ---
// 等待最老的阶段 (即第一阶段)
pipeline.consumer_wait();
// 此时 buffer[0...127] 的数据已经搬完了
// 注意:如果后面要跨线程访问 buffer,这里仍需 __syncthreads()
pipeline.consumer_release();
// 等待第二阶段
pipeline.consumer_wait();
// 此时 buffer[128...383] (即第2、3个 block) 数据就绪
pipeline.consumer_release();
// 等待第三阶段
pipeline.consumer_wait();
// 此时 buffer[384...511] 数据就绪
pipeline.consumer_release();
}
-
如果 :你只需要每个线程处理自己搬运的那个数据 (比如
my_val = buffer[threadIdx.x]),那么不需要__syncthreads()。因为consumer_wait()已经保证了当前线程发起的异步拷贝已完成。 -
但是 :如果你需要处理别人搬运的数据 (这通常才是使用共享内存的目的),那么在
consumer_wait()之后必须 加__syncthreads()。因为pipeline只能保证"我搬完了",不能保证"我隔壁线程也搬完了"。
API介绍
四个操作共同构成了一个闭环的资源流转系统 。要理解它们,最核心的直觉是:流水线(Pipeline)是一组有限的"车厢"(Stages),生产者装货,消费者取货并退还车厢。
我们可以把这四个操作拆解为两个阶段:获取/提交(生产者) 和 等待/释放(消费者)。
线程块分区的情况
producer_acquire 和 producer_commit
-
谁来操作? 只有被指定为 Producer 角色的线程。
-
必须同时吗? 是的。 * 数量必须对齐 :必须有整整
producer_count个线程调用了这两个方法,流水线才会推进到下一个逻辑阶段。- Warp 必须收敛:正如我们之前讨论的,如果 Warp 内部 32 个线程分先后调用,会因为消耗多个硬件槽位(Stage)而导致死锁。
consumer_wait 和 consumer_release
-
谁来操作? 只有被指定为 Consumer 角色的线程。
-
必须同时吗? 是的。
-
必须所有消费者线程都调用了
wait,它们才会一起被唤醒。 -
必须所有消费者都调用了
release,对应的硬件槽位(Stage)才会被标记为空闲,从而允许生产者进行下一次acquire。
-
核心逻辑 :在分区模式下,这四个操作就像是角色内部的屏障(Barrier)。生产者们必须集体行动,消费者们也必须集体行动。
producer_acquire() ------ 申请资源
-
动作:生产者向系统申请下一个可用的物理阶段(Stage)。
-
什么时候会阻塞? 当流水线所有的
stages_count都被占满且没被释放时。- 例子 :你初始化了 2 个 Stage,且已经
commit了两次,但消费者还没来得及release任何一个。此时第三次调用acquire就会立即阻塞。
- 例子 :你初始化了 2 个 Stage,且已经
-
什么时候放行? 当消费者调用
consumer_release()归还了一个旧Stage时,阻塞的生产者会被唤醒。
producer_commit() ------ 提交任务
-
动作:生产者告诉系统:"这个Stage我装好了(异步指令发完了),你可以推给消费者了。"
-
注意 :这个操作永远不会阻塞(除非发生 Warp 纠缠导致的资源瞬间耗尽),它只是一个状态标记。
consumer_wait() ------ 等待数据
-
动作:消费者查看流水线中最老的那节Stage(Tail),确认数据到哪了。
-
什么时候会阻塞? 两种情况:
-
生产者根本还没
commit。 -
生产者虽然
commit了,但后台的异步拷贝还没搬完。
-
-
什么时候放行? 当该阶段对应的所有异步内存操作全部完成时,阻塞的消费者会放行。
consumer_release() ------ 释放资源
-
动作:消费者告诉系统:"货取完了,这节车厢现在是空的,可以回收到仓库了。"
-
重要性 :它不阻塞自己,但它是解开生产者阻塞的唯一钥匙。
| 操作 | 目标 | 阻塞点 | 放行条件 |
|---|---|---|---|
producer_acquire |
空位 | 流水线满员(所有 Stage 都在排队) | 消费者执行了 consumer_release |
producer_commit |
标记 | 通常不阻塞 | N/A |
consumer_wait |
数据 | 生产者没干完,或数据还在路上 | 后台 异步拷贝引擎 完成搬运 |
consumer_release |
归还 | 不阻塞 | N/A |
线程私有的情况
同时,线程私有的情况还有些不一样,因为没有显式的Stage设置,所以阻塞上限在硬件限制
如果你在 make_pipeline 时没传 group 和 shared_state,那么这个流水线就是你一个人的。
-
必须同时吗? 完全不需要。
-
每个线程拥有自己独立的物理槽位(Stage)计数器。
-
线程 A 可以已经 commit 了 3 次,而线程 B 才刚刚开始第一次 acquire。它们互不干扰,也不会互相等待。
| 维度 | 线程块共享 (scope_block) | 线程私有 (scope_thread) |
|---|---|---|
| 阻塞原因 | 人为设定的缓冲区满了(比如你只设了 2 级缓冲)。 | 硬件处理能力到顶了 或 达到了流水线深度上限。 |
| 阻塞后果 | 全家停步 :只要有一个 Warp 卡在 acquire,整个 Block 进度受阻。 |
个体停步:线程 A 搬得太猛卡住了,线程 B 只要还有槽位,就能继续搬。 |
| 死锁风险 | 高:通常是因为生产者和消费者在同步点步调不一致。 | 低:因为没有跨线程竞争,除非你逻辑写死,否则很难死锁。 |
cuda::memcpy_async
cpp
template <typename _Group, thread_scope _Scope>
_LIBCUDACXX_INLINE_VISIBILITY async_contract_fulfillment memcpy_async(
_Group const& __group, void* __destination, void const* __source, std::size_t __size, pipeline<_Scope>& __pipeline);
使用流水线实现生产者-消费者模式
在上一节中,我们展示了如何通过空间划分线程块,并使用异步屏障来实现生产者-消费者模式。利用 cuda::pipeline,这一过程可以得到极大简化:通过一个分区流水线(Partitioned Pipeline)即可管理,每个数据缓冲区对应一个流水线阶段(Stage),而不再需要为每个缓冲区维护两个异步屏障。
在这个示例中,我们使用线程块中一半的线程作为生产者,另一半作为消费者。
-
第一步 :创建一个
cuda::pipeline对象。由于我们需要区分角色,必须使用cuda::thread_scope_block作用域的分区流水线。 -
初始化 :分区流水线需要
cuda::pipeline_shared_state来协调。我们在线程块作用域内初始化一个 2 阶段(Stage)的状态,并调用cuda::make_pipeline()。 -
预填充(Fill):生产者线程首先提交异步拷贝,填充流水线的所有阶段。此时所有数据拷贝都在进行中(In-flight)。
-
主循环:遍历所有数据块。根据线程角色,生产者提交未来批次的异步拷贝,消费者则消耗当前批次。
cpp
#include <cuda/pipeline>
#include <cooperative_groups.h>
#include <cuda_runtime.h>
#include <iostream>
#include <vector>
using pipeline_t = cuda::pipeline<cuda::thread_scope_block>;
// ==========================================
// 生产者逻辑
// ==========================================
__device__ void produce(pipeline_t &pipe, int stage, int batch, int num_batches,
float *buffer, int buffer_len, float *in) {
if (batch < num_batches) {
// 1. 申请空间:如果 Stage 0/1 都满了,这里会阻塞
pipe.producer_acquire();
// 2. 异步搬运:将数据从 Global 搬到对应的 Shared Memory 槽位
float* src = in + batch * buffer_len;
float* dst = buffer + stage * buffer_len;
// 每个生产者线程负责搬运一部分(步长为生产者线程数)
int producer_thread_count = blockDim.x / 2;
int tid = threadIdx.x;
for (int i = tid; i < buffer_len; i += producer_thread_count) {
cuda::memcpy_async(dst + i, src + i, sizeof(float), pipe);
}
// 3. 提交:标记该 Stage 已经下单完毕
pipe.producer_commit();
}
}
// ==========================================
// 消费者逻辑
// ==========================================
__device__ void consume(pipeline_t &pipe, int stage, int batch,
float *buffer, int buffer_len, float *out) {
// 1. 等待:直到该阶段的数据完全搬运到 Shared Memory
pipe.consumer_wait();
// 2. 干活:处理数据并写回 Global
float* src = buffer + stage * buffer_len;
float* dst = out + batch * buffer_len;
int consumer_thread_count = blockDim.x - (blockDim.x / 2);
int tid = threadIdx.x - (blockDim.x / 2);
for (int i = tid; i < buffer_len; i += consumer_thread_count) {
dst[i] = src[i] * 2.0f; // 业务逻辑
}
// 3. 释放:归还 Stage,让生产者可以再次 acquire
pipe.consumer_release();
}
// ==========================================
// 主 Kernel
// ==========================================
__global__ void producer_consumer_pattern(float *in, float *out, int N, int buffer_len) {
auto block = cooperative_groups::this_thread_block();
// 外部动态共享内存
extern __shared__ float buffer[];
const int num_batches = N / buffer_len;
constexpr int num_stages = 2; // 双缓冲
// 初始化分区流水线
size_t producer_count = block.size() / 2;
__shared__ cuda::pipeline_shared_state<cuda::thread_scope_block, num_stages> shared_state;
// 注意:所有线程都必须执行 make_pipeline,角色根据 thread_rank 分配
pipeline_t pipe = cuda::make_pipeline(block, &shared_state, producer_count);
// 1. 预热(Fill the pipeline):让生产者先把前 2 个缓冲区填满
if (block.thread_rank() < producer_count) {
for (int s = 0; s < num_stages; ++s) {
produce(pipe, s, s, num_batches, buffer, buffer_len, in);
}
}
// 2. 主流水线循环
int stage = 0;
for (int b = 0; b < num_batches; ++b) {
if (block.thread_rank() < producer_count) {
// 生产者:始终尝试"预取"未来第 N+2 个批次的数据
produce(pipe, stage, b + num_stages, num_batches, buffer, buffer_len, in);
} else {
// 消费者:消耗当前第 b 个批次
consume(pipe, stage, b, buffer, buffer_len, out);
}
// 关键:两个角色必须同步切换 Stage 指针
stage = (stage + 1) % num_stages;
}
}
// ==========================================
// Main 函数
// ==========================================
int main() {
const int N = 1024 * 10;
const int buffer_len = 1024;
size_t size = N * sizeof(float);
float *h_in = new float[N], *h_out = new float[N];
for(int i=0; i<N; ++i) h_in[i] = i;
float *d_in, *d_out;
cudaMalloc(&d_in, size);
cudaMalloc(&d_out, size);
cudaMemcpy(d_in, h_in, size, cudaMemcpyHostToDevice);
// 启动参数:128 线程(64 产 / 64 消),动态共享内存大小为 2 个 buffer
size_t shared_mem = 2 * buffer_len * sizeof(float);
producer_consumer_pattern<<<1, 128, shared_mem>>>(d_in, d_out, N, buffer_len);
cudaMemcpy(h_out, d_out, size, cudaMemcpyDeviceToHost);
std::cout << "Result [0]: " << h_out[0] << ", [1023]: " << h_out[1023] << std::endl;
cudaFree(d_in); cudaFree(d_out);
delete[] h_in; delete[] h_out;
return 0;
}
在进入主循环前,生产者先行动。
-
生产者 连续调用两次
produce:-
把
Batch 0搬到Stage 0。 -
把
Batch 1搬到Stage 1。
-
-
消费者 :在
if逻辑外,原地等待,不参与预热。 -
结果:此时硬件的异步拷贝引擎已经忙起来了,Global 的前两个批次数据正在飞往 Shared Memory。
循环第一轮 (b=0, stage=0):
-
消费者 :调用
consume处理Batch 0(位于Stage 0)。它会执行consumer_wait(),如果异步拷贝没完,它就等;完了就开工计算。 -
生产者 :调用
produce预取Batch 2(位于Stage 0)。- 注意 :此时
Stage 0正在被消费者使用。所以生产者的producer_acquire()会阻塞,直到消费者处理完Batch 0并执行consumer_release()。
- 注意 :此时
-
同步点 :当消费者放手
Stage 0,生产者立刻填入Batch 2。
循环第二轮 (b=1, stage=1):
-
消费者 :处理
Batch 1(位于Stage 1)。 -
生产者 :预取
Batch 3(覆盖到Stage 1)。 -
逻辑同上:消费者用旧的,生产者塞新的。