1# Function Flow Runtime图依赖并发(C) 2 3<!--Kit: Function Flow Runtime Kit--> 4<!--Subsystem: Resourceschedule--> 5<!--Owner: @chuchihtung; @yanleo--> 6<!--Designer: @geoffrey_guo; @huangyouzhong--> 7<!--Tester: @lotsof; @sunxuhao--> 8<!--Adviser: @foryourself--> 9 10## 概述 11 12FFRT图依赖并发范式支持任务依赖和数据依赖两种方式构建任务依赖图。任务依赖图中每个节点代表一个任务,边代表任务之间的依赖关系。任务依赖分为输入依赖`in_deps`和输出依赖`out_deps`。 13 14构建任务依赖图的两种不同方式: 15 16- 当使用任务依赖方式构建任务依赖图时,使用任务句柄`handle`来对应一个任务对象。 17- 当使用数据依赖方式构建任务依赖图时,数据对象表达抽象为数据签名,每个数据签名唯一对应一个数据对象。 18 19### 任务依赖 20 21> **说明:** 22> 23> 当任务句柄出现在一个任务的`in_deps`中时,任务句柄对应的任务是该任务的前置任务;当任务句柄出现在一个任务的`out_deps`中时,任务句柄对应的任务是该任务的后继任务。 24 25任务依赖适用于任务之间有明确顺序或逻辑流程要求的场景,例如: 26 27- 顺序执行的任务,例如:先进行数据预处理任务,然后再进行模型训练任务。 28- 逻辑流程控制,例如:商品交易过程需依次执行下单、制作和物流运输三个步骤。 29- 多级任务链,例如:流媒体视频处理过程中,视频解析后可以进行视频转码和视频生成缩略图,然后是视频添加水印,最后是视频发布。 30 31### 数据依赖 32 33> **说明:** 34> 35> 当数据对象的签名出现在一个任务的`in_deps`中时,该任务称为数据对象的消费者任务,消费者任务执行不改变其输入数据对象的内容; 36> 当数据对象的签名出现在任务的`out_deps`中时,该任务称为数据对象的生产者任务,生产者任务执行改变其输出数据对象的内容,从而生成该数据对象的一个新的版本。 37 38数据依赖适用于任务之间通过数据生产和消费关系来触发执行的场景。 39 40一个数据对象可能存在多个版本,每个版本对应一个生产者任务和零个,一个或多个消费者任务,根据生产者任务和消费者任务的下发顺序定义数据对象的多个版本的顺序,以及每个版本所对应的生产者和消费者任务。 41 42数据依赖解除的任务进入就绪状态允许被调度执行,依赖解除状态指任务所有输入数据对象版本的生产者任务执行完成,且所有输出数据对象版本的所有消费者任务执行完成的状态。 43 44FFRT在运行时可动态构建任务之间的基于生产者/消费者的数据依赖关系并遵循任务数据依赖状态执行调度,包括: 45 46- Producer-Consumer依赖 47 48 一个数据对象版本的生产者任务和该数据对象版本的消费者任务之间形成的依赖关系,也称为Read-after-Write依赖。 49 50- Consumer-Producer依赖 51 52 一个数据对象版本的消费者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Read依赖。 53 54- Producer-Producer依赖 55 56 一个数据对象版本的生产者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Write依赖。 57 58例如,存在一组任务与数据A的关系表述为: 59 60```cpp 61task1(OUT A); 62task2(IN A); 63task3(IN A); 64task4(OUT A); 65task5(OUT A); 66``` 67 68 69 70为表述方便,本文中的数据流图均以圆圈表示Task,方块表示数据。 71 72可以得出以下结论: 73 74- task1与task2/task3构成Producer-Consumer依赖,即:task2/task3需要等到task1写完A之后才能读A。 75- task2/task3与task4构成Consumer-Producer依赖,即:task4需要等到task2/task3读完A之后才能写A。 76- task4与task5构成Producer-Producer依赖,即:task5需要等到task4写完A之后才能写A。 77 78## 示例:流媒体视频处理 79 80用户上传视频到流媒体平台,处理步骤包含:视频解析A、视频转码B、视频缩略图生成C、视频水印添加D和视频发布E,其中步骤B和步骤C可以并行执行。任务流程如下图所示: 81 82 83 84借助FFRT提供了图依赖并发范式,可以描述任务依赖关系,同时并行化上述视频处理流程,代码如下所示: 85 86```c 87#include <stdio.h> 88#include "ffrt/ffrt.h" // 来自 OpenHarmony 第三方库 "@ppd/ffrt" 89 90void func_TaskA(void* arg) 91{ 92 printf("视频解析\n"); 93} 94 95void func_TaskB(void* arg) 96{ 97 printf("视频转码\n"); 98} 99 100void func_TaskC(void* arg) 101{ 102 printf("视频生成缩略图\n"); 103} 104 105void func_TaskD(void* arg) 106{ 107 printf("视频添加水印\n"); 108} 109 110void func_TaskE(void* arg) 111{ 112 printf("视频发布\n"); 113} 114 115int main() 116{ 117 // 提交任务A 118 ffrt_task_handle_t hTaskA = ffrt_submit_h_f(func_TaskA, NULL, NULL, NULL, NULL); 119 120 // 提交任务B和C 121 ffrt_dependence_t taskA_deps[] = {{ffrt_dependence_task, hTaskA}}; 122 ffrt_deps_t dTaskA = {1, taskA_deps}; 123 ffrt_task_handle_t hTaskB = ffrt_submit_h_f(func_TaskB, NULL, &dTaskA, NULL, NULL); 124 ffrt_task_handle_t hTaskC = ffrt_submit_h_f(func_TaskC, NULL, &dTaskA, NULL, NULL); 125 126 // 提交任务D 127 ffrt_dependence_t taskBC_deps[] = {{ffrt_dependence_task, hTaskB}, {ffrt_dependence_task, hTaskC}}; 128 ffrt_deps_t dTaskBC = {2, taskBC_deps}; 129 ffrt_task_handle_t hTaskD = ffrt_submit_h_f(func_TaskD, NULL, &dTaskBC, NULL, NULL); 130 131 // 提交任务E 132 ffrt_dependence_t taskD_deps[] = {{ffrt_dependence_task, hTaskD}}; 133 ffrt_deps_t dTaskD = {1, taskD_deps}; 134 ffrt_submit_f(func_TaskE, NULL, &dTaskD, NULL, NULL); 135 136 // 等待所有任务完成 137 ffrt_wait(); 138 139 ffrt_task_handle_destroy(hTaskA); 140 ffrt_task_handle_destroy(hTaskB); 141 ffrt_task_handle_destroy(hTaskC); 142 ffrt_task_handle_destroy(hTaskD); 143 return 0; 144} 145``` 146 147预期的输出可能为: 148 149```plain 150视频解析 151视频转码 152视频生成缩略图 153视频添加水印 154视频发布 155``` 156 157## 示例:斐波那契数列 158 159斐波那契数列中每个数字是前两个数字之和,计算斐波那契数的过程可以很好地通过数据对象来表达任务依赖关系。使用FFRT并发编程框架计算斐波那契数的代码如下所示: 160 161```c 162#include <stdio.h> 163#include "ffrt/ffrt.h" // 来自 OpenHarmony 第三方库 "@ppd/ffrt" 164 165typedef struct { 166 int x; 167 int* y; 168} fib_ffrt_s; 169 170void fib_ffrt(void* arg) 171{ 172 fib_ffrt_s* p = (fib_ffrt_s*)arg; 173 int x = p->x; 174 int* y = p->y; 175 176 if (x <= 1) { 177 *y = x; 178 } else { 179 int y1, y2; 180 fib_ffrt_s s1 = {x - 1, &y1}; 181 fib_ffrt_s s2 = {x - 2, &y2}; 182 183 // 构建数据依赖 184 ffrt_dependence_t dx_deps[] = {{ffrt_dependence_data, &x}}; 185 ffrt_deps_t dx = {1, dx_deps}; 186 ffrt_dependence_t dy1_deps[] = {{ffrt_dependence_data, &y1}}; 187 ffrt_deps_t dy1 = {1, dy1_deps}; 188 ffrt_dependence_t dy2_deps[] = {{ffrt_dependence_data, &y2}}; 189 ffrt_deps_t dy2 = {1, dy2_deps}; 190 ffrt_dependence_t dy12_deps[] = {{ffrt_dependence_data, &y1}, {ffrt_dependence_data, &y2}}; 191 ffrt_deps_t dy12 = {2, dy12_deps}; 192 193 // 分别提交任务 194 ffrt_submit_f(fib_ffrt, &s1, &dx, &dy1, NULL); 195 ffrt_submit_f(fib_ffrt, &s2, &dx, &dy2, NULL); 196 197 // 等待任务完成 198 ffrt_wait_deps(&dy12); 199 *y = y1 + y2; 200 } 201} 202 203int main() 204{ 205 int r; 206 fib_ffrt_s s = {5, &r}; 207 ffrt_dependence_t dr_deps[] = {{ffrt_dependence_data, &r}}; 208 ffrt_deps_t dr = {1, dr_deps}; 209 ffrt_submit_f(fib_ffrt, &s, NULL, &dr, NULL); 210 211 // 等待任务完成 212 ffrt_wait_deps(&dr); 213 printf("Fibonacci(5) is %d\n", r); 214 return 0; 215} 216``` 217 218预期输出为: 219 220```plain 221Fibonacci(5) is 5 222``` 223 224示例中将`fibonacci(x-1)`和`fibonacci(x-2)`作为两个任务提交给FFRT,在两个任务完成之后将结果进行累加。虽然单个任务只是拆分成两个子任务,但是子任务又可以继续进行拆分,因此整个计算图的并行度是非常高的。 225 226各个任务在FFRT内部形成了一棵调用树: 227 228 229 230## 接口说明 231 232上述样例中涉及到主要的FFRT的接口包括: 233 234| 名称 | 描述 | 235| ---------------------------------------------------------- | -------------------------------- | 236| [ffrt_submit_f](ffrt-api-guideline-c.md#ffrt_submit_f) | 提交任务调度执行。 | 237| [ffrt_submit_h_f](ffrt-api-guideline-c.md#ffrt_submit_h_f) | 提交任务调度执行并返回任务句柄。 | 238| [ffrt_wait_deps](ffrt-api-guideline-c.md#ffrt_wait_deps) | 等待依赖的任务完成。 | 239 240> **说明:** 241> 242> - 如何使用FFRT C++ API详见:[FFRT C++接口三方库使用指导](ffrt-development-guideline.md#using-ffrt-c-api-1)。 243> - 使用FFRT C接口或C++接口时,均可通过FFRT C++接口三方库简化头文件包含,即使用`#include "ffrt/ffrt.h"`头文件包含语句。 244 245## 约束限制 246 247- 使用`ffrt_submit_base`接口进行任务提交时,每个任务的输入依赖和输出依赖的数量之和不能超过8个。 248- 使用`ffrt_submit_h_base`接口进行任务提交时,每个任务的输入依赖和输出依赖的数量之和不能超过7个。 249- 当参数同时作为输入依赖和输出依赖时,统计依赖数量时只统计一次,如输入依赖是`{&x}`,输出依赖也是`{&x}`,实际依赖的数量是1。 250