• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![image](figures/ffrt_figure3.png)
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![image](figures/ffrt_figure1.png)
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![image](figures/ffrt_figure2.png)
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