• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1## FFRT 用户编程指南
2
3> Function Flow编程模型是一种基于任务和数据驱动的并发编程模型,允许开发者通过任务及其依赖关系描述的方式进行应用开发。FFRT(Function Flow运行时)是支持Function Flow编程模型的软件运行时库,用于调度执行开发者基于Function Flow编程模型开发的应用。通过Function Flow编程模型和FFRT,开发者可专注于应用功能开发,由FFRT在运行时根据任务依赖状态和可用执行资源自动并发调度和执行任务。
4>
5> 本文用于指导开发者基于Function Flow编程模型和FFRT实现并行编程。
6
7<hr/>
8# 版本
9
10| 版本 | 编辑                                                         | 主要变更                                                     | 日期       |
11| ---- | ------------------------------------------------------------ | ------------------------------------------------------------ | ---------- |
12| V0.1 | linjiashu <br />zhangguowei <br />huangyouzhong  | 发布以下API:<br />1. task 管理,包括:submit,wait,task_attr, task_handle/submit_h<br />2. 同步原语,包括:mutex,condition_variable<br />3. Deadline 调度<br />4. 杂项:sleep,yield<br /> | 2022/09/26 |
13
14
15<br/>
16
17<hr/>
18# 缩写
19
20| 缩略语        | 英文全名                        | 中文解释                                                     |
21| ------------- | ------------------------------- | ------------------------------------------------------------ |
22| FFRT          | Function Flow Run Time          | 软件实现Function Flow运行时用于任务调度和执行                |
23| Function Flow | Function Flow Programming Model | Function Flow编程模型                                        |
24| Pure Function | Pure Function                   | 纯函数,注意本文中定义的纯函数指的是通过表达相互间数据依赖即可由调度系统保证正确执行的任务。 |
25
26
27<br/>
28<hr/>
29# 编程模型
30## 两种编程模型
31
32|                | 线程编程模型                                                 | FFRT任务编程模型                                             |
33| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
34| 并行度挖掘方式 | 程序员通过创建多线程并把任务分配到每个线程中执行来挖掘运行时的并行度 | 程序员(编译器工具或语言特性配合)静态编程时将应用分解成任务及其数据依赖关系,运行时调度器分配任务到工作线程执行 |
35| 谁负责线程创建 | 程序员负责创建线程,线程编程模型无法约束线程的创建,滥用可能造成系统中大量线程 | FFRT运行时负责工作线程池的创建和管理由调度器负责,程序员无法直接创建线程 |
36| 负载均衡       | 程序员静态编程时将任务映射到线程,映射不合理或任务执行时间不确定造成线程负载不均 | FFRT运行时根据线程执行状态调度就绪任务到空闲线程执行,减轻了线程负载不均问题 |
37| 调度开销       | 线程调度由内核态调度器完成,调度开销大                       | FFRT运行时在用户态以协程方式调度执行,相比内核线程调度机制更为轻量,减小调度的开销,并可通过硬化调度卸载进一步减小调度开销 |
38| 依赖表达       | 线程创建时即处于可执行状态,执行时与其他线程同步操作,增加线程切换 | FFRT运行时根据任务创建时显式表达的输入依赖和输出依赖关系判断任务可执行状态,当输入依赖不满足时,任务不被调度执行 |
39
40
41
42## Function Flow 任务编程模型
43
44Function Flow编程模型允许开发者通过任务及其依赖关系描述的方式进行应用开发,其主要特性包括`Task-Based` 和 `Data-Driven` 。
45
46### Task-Based 特性
47
48`Task-Based` 指在Function Flow编程模型中开发者以任务方式来组织应用程序表达,运行时以任务粒度执行调度。
49
50任务定义为一种面向开发者的编程线索和面向运行时的执行对象,通常包含一组指令序列及其操作的数据上下文环境。
51
52Function Flow编程模型中的任务包含以下主要特征:
53
54- 任务之间可指定依赖关系,依赖关系通过`Data-Driven`方式表达。
55- 任务可支持嵌套,即任务在执行过程中可生成新的任务下发给运行时,形成父子任务关系。
56- 多任务支持互同步操作,例如等待,锁,条件变量等。
57
58> 注意
59>
60> 任务颗粒度影响应用执行性能,颗粒度过小增加调度开销,颗粒度过大降低并行度。Function Flow编程模型中任务的目标颗粒度最小为100us量级,开发者应注意合理控制任务颗粒度。
61
62### Data-Driven 特性
63
64`Data-Driven`指任务之间的依赖关系通过数据依赖表达。
65
66任务执行过程中对其关联的数据对象进行读写操作。在Function Flow编程模型中,数据对象表达抽象为数据签名,每个数据签名唯一对应一个数据对象。
67
68数据依赖抽象为任务所操作的数据对象的数据签名列表,包括输入数据依赖`in_deps`和输出数据依赖`out_deps`。数据对象的签名出现在一个任务的`in_deps`中时,该任务称为数据对象的消费者任务,消费者任务执行不改变其输入数据对象的内容;数据对象的签名出现在任务的`out_deps`中时,该任务称为数据对象的生产者任务,生产者任务执行改变其输出数据对象的内容,从而生成该数据对象的一个新的版本。
69
70一个数据对象可能存在多个版本,每个版本对应一个生产者任务和零个,一个或多个消费者任务,根据生产者任务和消费者任务的下发顺序定义数据对象的多个版本的顺序以及每个版本所对应的生产者和消费者任务。
71
72数据依赖解除的任务进入就绪状态允许被调度执行,依赖解除状态指任务所有输入数据对象版本的生产者任务执行完成,且所有输出数据对象版本的所有消费者任务执行完成的状态。
73
74通过上述`Data-Driven`的数据依赖表达,FFRT在运行时可动态构建任务之间的基于生产者/消费者的数据依赖关系并遵循任务数据依赖状态执行调度,包括:
75
76- Producer-Consumer 依赖
77
78  一个数据对象版本的生产者任务和该数据对象版本的消费者任务之间形成的依赖关系,也称为Read-after-Write依赖。
79
80- Consumer-Producer 依赖
81
82  一个数据对象版本的消费者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Read依赖。
83
84- Producer-Producer 依赖
85
86  一个数据对象版本的生产者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Write依赖。
87
88
89例如,如果有这么一些任务,与数据A的关系表述为:
90```{.cpp}
91task1(OUT A);
92task2(IN A);
93task3(IN A);
94task4(OUT A);
95task5(OUT A);
96```
97
98<img src="images/image-20220926150341102.png" style="zoom:60%" />
99
100> 为表述方便,本文中的数据流图均以圆圈表示 Task,方块表示数据。
101
102可以得出以下结论:
103- task1 与task2/task3 构成Producer-Consumer 依赖,即:task2/task3 需要等到task1 写完A之后才能读A
104- task2/task3 与task4 构成Consumer-Producer 依赖,即:task4 需要等到task2/task3 读完A之后才能写A
105- task4 与task5 构成Producer-Producer 依赖,即:task5 需要等到task4 写完A之后才能写A
106
107
108
109# C++ API
110
111> C++ API采用接近C++11的命名风格,以`ffrt`命名空间替代`std`命名空间
112> 需编译使用-std=c++17
113
114## 任务管理
115### submit
116<hr/>
117* 向调度器提交一个task
118* 该接口是异步的,即该接口不等到task完成即可返回,因此,通常与[wait](#wait) 配合使用
119
120#### 声明
121
122```{.cpp}
123namespace ffrt {
124void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {});
125void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {});
126}
127```
128
129#### 参数
130
131`func`
132
133* 可被std::function 接收的一切CPU 可执行体,可以为C++ 定义的Lambda 函数闭包,函数指针,甚至是函数对象
134
135`in_deps`
136
137* 该参数是可选的
138* 该参数用于描述该任务的输入依赖,FFRT 通过数据的虚拟地址作为数据的Signature 来建立依赖
139
140`out_deps`
141
142* 该参数是可选的
143* 该参数用于描述该任务的输出依赖
144* `注意`:该依赖值本质上是一个数值,ffrt没办法区分该值是合理的还是不合理的,会假定输入的值是合理的进行处理;但不建议采用NULL,1, 2 等值来建立依赖关系,建议采用实际的内存地址,因为前者使用不当会建立起不必要的依赖,影响并发
145
146`attr`
147
148* 该参数是可选的
149* 该参数用于描述Task 的属性,比如qos 等,详见 [task_attr](#task_attr)章节
150
151#### 返回值
152
153* 不涉及
154
155#### 描述
156* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用
157
158* 该接口支持嵌套调用,即任务中可以继续提交子任务
159
160* 该接口在实现上使用多个重载版本以优化性能(基于移动语义,初始化列表等),用户只需按上述原型使用,编译时会自动选择最佳版本,支持的重载版本有:
161
162  ```{.cpp}
163  void submit(std::function<void()>&& func);
164  void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps);
165  void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps);
166  void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps, const task_attr& attr);
167
168  void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps);
169  void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps);
170  void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps, const task_attr& attr);
171
172  void submit(const std::function<void()>& func);
173  void submit(const std::function<void()>& func, std::initializer_list<const void*> in_deps);
174  void submit(const std::function<void()>& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps);
175  void submit(const std::function<void()>& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps, const task_attr& attr);
176
177  void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps);
178  void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps);
179  void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps, const task_attr& attr);
180  ```
181
182
183#### 样例
184
185**submit and wait**
186
187```{.cpp}
188#include <iostream>
189#include "ffrt.h"
190
191int main(int narg, char** argv)
192{
193    int i = 0;
194    for (i = 0; i < 3; i++) {
195        ffrt::submit([i] { std::cout << "num: " << i << std::endl; });
196    }
197    ffrt::wait();
198    return 0;
199}
200```
201
202`解析`:
203
2041)   该示例中连续下发了3个Task,Task 使用C++ 11 Lambda 来描述(实际中Task 还可以使用函数指针,函数对象来描述),这些 Task 都会读取i,但是不会写任何变量;
205
2062)   ffrt::submit 为异步下发,因此,Task2 并不会等到 Task1 执行完成之后再下发;
207
2083)   ffrt::wait 用于实现待所有Task 都执行完成之后 main 函数再退出;
209
2104)   由于3个Task 在数据依赖关系上没有生产者-消费者或生产者-生产者依赖关系,因此3个 Task 是可以并行的,1种可能的输出是:
211
212```{.cpp}
213num: 0
214num: 2
215num: 1
216```
217
218`注意`:
219
220如果将Lambda 表达式中的值捕获设置成引用捕获(即`[&i] { std::cout << "num: " << i << std::endl; }`),可能得到的输出为:
221
222```{.cpp}
223num: 2
224num: 2
225num: 2
226```
227
228这是因为FFRT 是异步编程模型,在第一个task 真正开始执行的时候,i 的值可能已经被修改为1或者2
229
230
231
232**data verison**
233
234<img src="images/image-20220926150341102.png" style="zoom:60%" />
235
236```{.cpp}
237#include <iostream>
238#include "ffrt.h"
239
240int main(int narg, char** argv)
241{
242    int x = 1;
243    ffrt::submit([&] {x = 100; std::cout << "x:" << x << std::endl;}, {}, {&x});
244    ffrt::submit([&] {std::cout << "x:" << x << std::endl;}, {&x}, {});
245    ffrt::submit([&] {std::cout << "x:" << x << std::endl;}, {&x}, {});
246    ffrt::submit([&] {x++; std::cout << "x:" << x << std::endl;}, {}, {&x});
247    ffrt::submit([&] {x++; std::cout << "x:" << x << std::endl;}, {}, {&x});
248
249    ffrt::wait();
250    return 0;
251}
252```
253
254 `解析`:
255
2561)   按上一章节[Data-Driven 特性](#Data-Driven 特性)的描述,输出一定为:
257
258```{.cpp}
259x:100
260x:100
261x:100
262x:101
263x:102
264```
265
266**nested task**
267
268```{.cpp}
269#include <iostream>
270#include "ffrt.h"
271
272int main(int narg, char** argv)
273{
274    ffrt::submit([&] {
275        std::cout << "task 1" << std::endl;
276        ffrt::submit([&] {std::cout << "nested task 1.1" << std::endl;}, {}, {});
277        ffrt::submit([&] {std::cout << "nested task 1.2" << std::endl;}, {}, {});
278        ffrt::wait();
279    }, {}, {});
280
281    ffrt::submit([&] {
282        std::cout << "task 2" << std::endl;
283        ffrt::submit([&] {std::cout << "nested task 2.1" << std::endl;}, {}, {});
284        ffrt::submit([&] {std::cout << "nested task 2.2" << std::endl;}, {}, {});
285        ffrt::wait();
286    }, {}, {});
287    ffrt::wait();
288    return 0;
289}
290```
291
292 `解析`:
293
2941)   FFRT允许在 Task 内部继续提交多个SubTask,这样 Task 之间可以建立起一颗调用树;
295
2962)   Task1 和Task2 可以并行,Task 1.1/1.2/2.1/2.2 之间也可以并行,因此1种可行的输出为:
297
298```
299task 1
300nested task  1.1
301task 2
302nested task 1.2
303nested task 2.2
304nested task 2.1
305```
306
307### wait
308<hr/>
309* 同步等待,与[submit](#submit) 配合使用
310* 等待指定的数据被生产完成,或等待当前任务的所有子任务完成,在不满足条件之前,当前的执行上下文被suspend,在满足条件后恢复执行
311
312#### 声明
313
314```{.cpp}
315namespace ffrt {
316void wait(const std::vector<const void*>& deps);
317void wait();
318}
319```
320
321#### 参数
322
323`deps`
324
325* 需要等待被生产完成的数据的虚拟地址,这些地址可能作为某些任务在submit 时的out_deps
326
327#### 返回值
328
329* 不涉及
330
331#### 描述
332* wait(deps) 用于等待deps指代的数据被生产完成才能执行后面的代码
333* wait() 用于等待当前上下文提交的所有子任务(`注意:不包括孙子任务`)都完成才能执行后面的代码
334* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用
335* 在FFRT task 外部调用的wait 是OS 能够感知的等待,相对于FFRT task 内部调用的wait 是更加昂贵的,因此我们希望尽可能让更多的wait 发生在FFRT task 内部 ,而不是FFRT task 外部
336
337#### 样例
338
339**recursive fibonacci**
340
341串行版的fibonacci 可以实现为:
342
343```{.cpp}
344#include <iostream>
345
346void fib(int x, int& y) {
347    if (x <= 1) {
348        y = x;
349    } else {
350        int y1, y2;
351        fib(x - 1, y1);
352        fib(x - 2, y2);
353        y = y1 + y2;
354    }
355}
356
357int main(int narg, char** argv)
358{
359    int r;
360    fib(10, r);
361    std::cout << "fibonacci 10: " << r << std::endl;
362    return 0;
363}
364```
365
366若要使用 FFRT 实现并行(注,对于单纯的fibonacci,单个 Task 的计算量极小,不具有并行加速的意义,但这种调用pattern 对并行编程模型的灵活性考验是非常高的),其中1种可行的实现为:
367
368```{.cpp}
369#include <iostream>
370
371#include "ffrt.h"
372
373void fib_ffrt(int x, int& y)
374{
375    if (x <= 1) {
376        y = x;
377    } else {
378        int y1, y2;
379        ffrt::submit([&] {fib_ffrt(x - 1, y1);}, {&x}, {&y1} );
380        ffrt::submit([&] {fib_ffrt(x - 2, y2);}, {&x}, {&y2} );
381        ffrt::wait({&y1, &y2});
382        y = y1 + y2;
383    }
384}
385
386int main(int narg, char** argv)
387{
388    int r;
389    ffrt::submit([&] { fib_ffrt(10, r); }, {}, {&r});
390    ffrt::wait({&r});
391    std::cout << "fibonacci 10: " << r << std::endl;
392    return 0;
393}
394```
395
396`解析`:
397
3981)   将fibonacci (x-1)和fibonacci (x-2) 作为2个Task 提交给FFRT,在两个Task 完成之后将结果累加;
399
4002)   虽然单个Task 只能拆分成2个SubTask 但是子Task 可以继续拆分,因此,整个计算图的并行度是非常高的,Task 之间在FFRT 内部形成了一颗调用树;
401
402<img src="images/image-20220926152331554.png" style="zoom:100%" />
403
404
405### task_attr
406<hr/>
407* 定义task 的属性的辅助类,与[submit](#submit) 配合使用
408
409#### 声明
410
411```{.cpp}
412namespace ffrt {
413enum qos {
414    qos_inherit = -1,
415    qos_background,
416    qos_utility,
417    qos_default,
418    qos_user_initiated,
419};
420
421class task_attr {
422public:
423    task_attr& qos(enum qos qos); // set qos
424    enum qos qos() const; // get qos
425    task_attr& name(const char* name); // set name
426    const char* name() const; // get name
427};
428}
429```
430
431#### 参数
432
433`qos`
434
435* qos 设定的枚举类型
436* inherent 是一个qos 设定策略,代表即将submit 的task 的qos 继承当前task 的qos
437
438#### 返回值
439
440* 不涉及
441
442#### 描述
443* 约定
444  * 在submit 时,如果不通过task_attr 设定qos,那么默认该提交的task的qos 为`qos_default`
445  * 在submit 时,如果通过task_attr 设定qos 为`qos_inherent`,表示将该提交的task 的qos 与当前task 的qos 相同,在FFRT task 外部提交的属性为`qos_inherent` 的task,其qos 为`qos_default`
446  * 其他情况下,该提交的task 的qos 被设定为指定的值
447* qos 级别从上到下依次递增,qos_user_interactive拥有最高优先级
448
449#### 样例
450
451```{.cpp}
452#include <iostream>
453#include "ffrt.h"
454
455int main(int narg, char** argv)
456{
457    ffrt::submit([] { std::cout << "hello ffrt" << std::endl; }, {}, {},
458        ffrt::task_attr().qos(ffrt::qos_background));
459    ffrt::wait();
460    return 0;
461}
462```
463
464* 提交一个qos 级别为background 的任务
465
466
467
468### submit_h
469
470<hr/>
471
472* 向调度器提交一个task,与[submit](#submit) 的差别在于返回task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步
473
474#### 声明
475
476```{.cpp}
477namespace ffrt {
478class task_handle {
479public:
480    task_handle();
481    task_handle(ffrt_task_handle_t p);
482
483    task_handle(task_handle const&) = delete;
484    void operator=(task_handle const&) = delete;
485
486    task_handle(task_handle&& h);
487    task_handle& operator=(task_handle&& h);
488
489    operator void* () const;
490};
491
492task_handle submit_h(std::function<void()>&& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {});
493task_handle submit_h(const std::function<void()>& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {});
494}
495```
496
497#### 参数
498
499`func`
500
501* 同submit,详见[submit](#submit) 定义
502
503`in_deps`
504
505* 同submit,详见[submit](#submit) 定义
506
507`out_deps`
508
509* 同submit,详见[submit](#submit) 定义
510
511`attr`
512
513* 同submit,详见[submit](#submit) 定义
514
515#### 返回值
516
517* task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步
518
519#### 描述
520
521* 该接口与submit 使用基本相同,从性能的角度,在不需要返回task handle 的场景,可以调用submit 接口相对于submit_h 有更好的性能
522* task_handle 可以和其他的数据depends 同时作为某个task 的in_deps,表示该task 的执行依赖task_handle 对应的task 执行完成
523* task_handle 可以和其他的数据depends 同时作为wait 的deps,表示当前任务将被suspend,直到task_handle 对应的task 执行完成后将被恢复
524* task_handle 不建议作为某个task 的out_deps,其行为是未定义的
525
526
527#### 样例
528
529```{.cpp}
530#include <iostream>
531#include "ffrt.h"
532
533int main(int narg, char** argv)
534{
535    // handle work with submit
536    ffrt::task_handle h = ffrt::submit_h([] { std::cout << "hello "; }); // not need some data in this task
537    int x = 1;
538    ffrt::submit([&] { x++; }, {}, {&x});
539    ffrt::submit([&] { std::cout << "world, x = " << x << std::endl; }, {&x, h}); // this task depend x and h
540
541    // handle work with wait
542    ffrt::task_handle h2 = ffrt::submit_h([&] { std::cout << "handle wait" << std::endl; x++; });
543    ffrt::wait({h2});
544    std::cout << "x = " << x << std::endl;
545    ffrt::wait();
546    return 0;
547}
548```
549
550* 预期的输出为
551
552```
553hello world, x = 2
554handle wait
555x = 3
556```
557
558### get_id
559
560<hr/>
561
562* 返回当前task的id标识,更多使用用于维测(原因是task name可能重名)
563
564#### 声明
565
566```{.cpp}
567namespace ffrt {
568namespace this_task {
569uint64_t get_id();
570}
571}
572```
573
574#### 参数
575
576* 不涉及
577
578#### 返回值
579
580* 当前task的id
581
582#### 描述
583
584* 该接口在task内部调用将返回当前task的id标识,在task外部调用将返回0
585* 可以基于该接口在task外部调用返回0的特性来区分函数是运行在FFRT 工作线程上还是非FFRT工作线程上
586* task id为从1开始编码,每提交一个task便增加1,被设计成64bit,即便是每秒百万次提交,也需要292471.2年才会发生翻转
587
588#### 样例
589
590```{.cpp}
591#include <iostream>
592#include "ffrt.h"
593
594int main(int narg, char** argv)
595{
596    ffrt::submit([] { std::cout << "task id: " << ffrt::this_task::get_id() << std::endl; });
597    ffrt::submit([] { std::cout <<"task id: " << ffrt::this_task::get_id() << std::endl; });
598    ffrt::wait();
599    std::cout << "task id: " << ffrt::this_task::get_id() << std::endl;
600    return 0;
601}
602```
603
604* 可能的输出为:
605
606```
607task id: 1
608task id: 2
609task id: 0
610```
611
612### update_qos
613
614<hr/>
615
616* 更新当前正在执行的task的优先级
617
618#### 声明
619
620```{.cpp}
621namespace ffrt {
622namespace this_task {
623int update_qos(enum qos qos);
624}
625}
626```
627
628#### 参数
629
630`qos`
631* 新的qos等级
632
633#### 返回值
634
635* 0表示成功,非0表示失败
636
637#### 描述
638
639* 该接口对当前task的qos调整会立即生效
640* 如果新设定的qos与当前的qos不一致,则会block当前task的执行,再按照新的qos恢复执行
641* 如果新设定的qos与当前的qos一致,则接口会立即返回,不做任何处理
642* **如果在非task内部调用该接口,则返回非0值,用户可以选择忽略或其他处理**
643
644#### 样例
645
646```{.cpp}
647#include <iostream>
648#include <thread>
649#include "ffrt.h"
650
651int main(int narg, char** argv)
652{
653    ffrt::submit([] {
654        std::cout << "thread id: " << std::this_thread::get_id() << std::endl;
655        std::cout << "return " << ffrt::this_task::update_qos(ffrt::qos_user_initiated) << std::endl;
656        std::cout << "thread id: " << std::this_thread::get_id() << std::endl;
657    });
658    ffrt::wait();
659    std::cout << "return " << ffrt::this_task::update_qos(ffrt::qos_user_initiated) << std::endl;
660    return 0;
661}
662```
663
664* 可能的输出为:
665
666```
667thread id: 1024
668return 0
669thread id: 2222
670return 1
671```
672
673
674
675## 同步原语
676
677### mutex
678<hr/>
679* FFRT提供的类似std::mutex 的性能实现
680
681#### 声明
682
683```{.cpp}
684namespace ffrt {
685class mutex {
686public:
687    mutex(mutex const &) = delete;
688    void operator =(mutex const &) = delete;
689
690    void lock();
691    void unlock();
692    bool try_lock();
693};
694}
695```
696
697#### 参数
698
699* 不涉及
700
701#### 返回值
702
703* 不涉及
704
705#### 描述
706* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为
707* 该功能能够避免传统的std::mutex 在抢不到锁时陷入内核的问题,在使用得当的条件下将会有更好的性能
708
709#### 样例
710
711```{.cpp}
712#include <iostream>
713#include "ffrt.h"
714
715void ffrt_mutex_task()
716{
717    int sum = 0;
718    ffrt::mutex mtx;
719    for (int i = 0; i < 10; i++) {
720        ffrt::submit([&sum, i, &mtx] {
721             mtx.lock();
722             sum = sum + i;
723             mtx.unlock();
724        }, {}, {});
725    }
726    ffrt::wait();
727    std::cout << "sum = " << sum << std::endl;
728}
729
730int main(int narg, char** argv)
731{
732    int r;
733    ffrt::submit(ffrt_mutex_task);
734    ffrt::wait();
735    return 0;
736}
737```
738
739预期输出为
740
741```
742sum=45
743```
744
745* 该例子为功能示例,实际中并不鼓励这样使用
746
747
748### condition_variable
749<hr/>
750
751* FFRT提供的类似std::condition_variable 的性能实现
752
753#### 声明
754
755```{.cpp}
756namespace ffrt {
757enum class cv_status {
758    no_timeout,
759    timeout
760};
761
762class condition_variable {
763public:
764    using TimePoint = std::chrono::steady_clock::time_point;
765    template<typename Clock, typename Duration, typename Pred>
766    bool wait_until(std::unique_lock<mutex>& lk,
767            const std::chrono::time_point<Clock, Duration>& tp,
768            Pred&& pred) noexcept;
769
770    template<typename Clock, typename Duration>
771    cv_status wait_until(std::unique_lock<mutex>& lk,
772            const std::chrono::time_point<Clock, Duration>& tp) noexcept;
773
774    template<typename Rep, typename Period>
775    cv_status wait_for(std::unique_lock<mutex>& lk,
776            const std::chrono::duration<Rep, Period>& sleep_time) noexcept;
777
778    template<typename Rep, typename Period, typename Pred>
779    bool wait_for(std::unique_lock<mutex>& lk,
780            const std::chrono::duration<Rep, Period>& sleepTime,
781            Pred&& pred) noexcept;
782
783    void wait(std::unique_lock<mutex>& lk);
784
785    template<typename Pred>
786    void wait(std::unique_lock<mutex>& lk, Pred&& pred);
787
788    void notify_one() noexcept;
789
790    void notify_all() noexcept;
791};
792}
793```
794
795#### 参数
796
797`lk`
798* mutex互斥量
799`tp`
800* 等待时间
801`sleep_time`
802* 等待时间
803`pred`
804* 检查是否等待函数
805#### 返回值
806
807* 不涉及
808
809#### 描述
810* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为
811* 该功能能够避免传统的std::condition_variable  在条件不满足时陷入内核的问题,在使用得当的条件下将会有更好的性能
812
813#### 样例
814
815```{.cpp}
816#include <iostream>
817#include "ffrt.h"
818
819void ffrt_cv_task()
820{
821    ffrt::condition_variable cond;
822    int a = 0;
823    ffrt::mutex lock_;
824    ffrt::submit([&] {
825        std::unique_lock lck(lock_);
826        cond.wait(lck, [&] { return a == 1; });
827        std::cout << "a = " << a << std::endl;
828    }, {}, {});
829    ffrt::submit([&] {
830        std::unique_lock lck(lock_);
831        a = 1;
832        cond.notify_one();
833    }, {}, {});
834
835    ffrt::wait();
836}
837
838int main(int narg, char** argv)
839{
840    int r;
841    ffrt::submit(ffrt_cv_task);
842    ffrt::wait();
843    return 0;
844}
845
846```
847
848预期输出为:
849
850```
851a=1
852```
853
854* 该例子为功能示例,实际中并不鼓励这样使用
855
856## 杂项
857
858### sleep
859
860<hr/>
861* FFRT提供的类似std::this_thread::sleep_for / std::this_thread::sleep_until 的性能实现
862
863#### 声明
864
865```{.cpp}
866namespace ffrt {
867namespace this_task {
868template<class _Rep, class _Period>
869void sleep_for(const std::chrono::duration<_Rep, _Period>& sleep_duration);
870
871template<class _Clock, class _Duration>
872void sleep_until(const std::chrono::time_point<_Clock, _Duration>& sleep_time);
873}
874}
875```
876
877#### 参数
878
879`sleep_duration`
880
881* 睡眠的时长
882
883`sleep_time`
884
885* 睡眠到达的时间点
886
887#### 返回值
888
889* 不涉及
890
891#### 描述
892* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为
893* 该功能能够避免传统的std::this_thread::sleep_for 睡眠时陷入内核的问题,在使用得当的条件下将会有更好的性能
894* 该接口调用后实际睡眠时长不小于配置值
895
896#### 样例
897
898```{.cpp}
899#include <chrono>
900#include <iostream>
901#include "ffrt.h"
902
903using namespace std::chrono_literals;
904int main(int narg, char** argv)
905{
906    ffrt::submit([] {
907        std::cout << "Hello waiter\n" << std::flush;
908        auto start = std::chrono::high_resolution_clock::now();
909        ffrt::this_task::sleep_for(2000ms);
910        auto end = std::chrono::high_resolution_clock::now();
911        std::chrono::duration<double, std::milli> elapsed = end-start;
912        std::cout << "Waited " << elapsed.count() << " ms\n";
913    });
914    ffrt::wait();
915    return 0;
916}
917```
918
919* 预期输出为
920
921```
922Hello waiter
923Waited 2000.12 ms
924```
925
926### yield
927<hr/>
928* 当前task 主动让出CPU 执行资源,让其他可以被执行的task 先执行,如果没有其他可被执行的task,yield 无效
929
930#### 声明
931
932```{.cpp}
933namespace ffrt {
934namespace this_task {
935void yield();
936}
937}
938```
939
940#### 参数
941
942* 不涉及
943
944#### 返回值
945
946* 不涉及
947
948#### 描述
949* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为
950* 此函数的确切行为取决于实现,特别是使用中的FFRT 调度程序的机制和系统状态
951
952#### 样例
953
954```{.cpp}
955#include <chrono>
956#include "ffrt.h"
957
958using namespace std::chrono_literals;
959// "busy sleep" while suggesting that other tasks run
960// for a small amount of time
961void little_sleep(std::chrono::microseconds us)
962{
963    auto start = std::chrono::high_resolution_clock::now();
964    auto end = start + us;
965    do {
966        ffrt::this_task::yield();
967    } while (std::chrono::high_resolution_clock::now() < end);
968}
969
970int main(int narg, char** argv)
971{
972    ffrt::submit([] { little_sleep(200us); });
973    ffrt::wait();
974    return 0;
975}
976```
977
978* 这是一个`busy sleep`,同时允许其他可以被执行的task 插入执行
979
980
981# C API
982
983> C API采用接近C11/pthread (https://zh.cppreference.com/w/c) 的命名风格,并冠以`ffrt_`前缀,以`_base`为后缀的API是内部API,通常不被用户直接调用
984>
985> **出于易用性方面的考虑,除非必要,强烈建议你使用C++ API(亦满足二进制兼容要求),调用C API将会使你的代码非常臃肿**
986
987## 任务管理
988
989### ffrt_submit_base
990
991* 该接口为ffrt动态库的导出接口,基于此可以封装出不同的C++ API ffrt::submit和C API ffrt_submit,满足二进制兼容
992
993#### 声明
994
995```{.cpp}
996const int ffrt_auto_managed_function_storage_size = 64 + sizeof(ffrt_function_header_t);
997typedef enum {
998    ffrt_function_kind_general,
999    ffrt_function_kind_queue
1000} ffrt_function_kind_t;
1001
1002void* ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_t kind);
1003
1004typedef void(*ffrt_function_t)(void*);
1005typedef struct {
1006    ffrt_function_t exec;
1007    ffrt_function_t destroy;
1008    uint64_t reserve[2];
1009} ffrt_function_header_t;
1010
1011void ffrt_submit_base(ffrt_function_header_t* func, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr);
1012```
1013
1014#### 参数
1015
1016`kind`
1017
1018* function子类型,用于优化内部数据结构,默认使用ffrt_function_kind_general类型
1019
1020`func`
1021
1022* CPU Function的指针,该指针执行的数据结构,按照`ffrt_function_header_t`定义的描述了该CPU Task如何执行和销毁的函数指针,FFRT通过这两个函数指针完成Task的执行和销毁
1023
1024`in_deps`
1025
1026* 同ffrt_submit
1027
1028
1029`out_deps`
1030
1031* 同ffrt_submit
1032
1033`attr`
1034
1035* 同ffrt_submit
1036
1037#### 返回值
1038
1039* 不涉及
1040
1041#### 描述
1042
1043* ffrt_submit_base不建议用户直接调用,推荐使用基于此封装的C++接口(亦满足二进制兼容)
1044* **ffrt_submit_base作为底层能力,只有在用户需要自定义task类型时使用,使用时需要满足以下限制:**
1045  * ffrt_submit_base入参中的func指针只能通过ffrt_alloc_auto_managed_function_storage_base申请,且二者的调用需一一对应
1046  * ffrt_alloc_auto_managed_function_storage_base申请的内存为ffrt_auto_managed_function_storage_size字节,其生命周期归ffrt管理,在该task结束时,由FFRT自动释放,用户无需释放
1047* ffrt_function_header_t 中定义了两个函数指针:
1048  * exec:用于描述该Task如何被执行,当FFRT需要执行该Task时由FFRT调用
1049  * destroy:用于描述该Task如何被执行,当FFRT需要执行该Task时由FFRT调用
1050
1051#### 样例
1052
1053* 通过该接口提供C++11 Lambda表达式的支持(该代码已经在ffrr.h中提供,默认支持)
1054
1055```{.cpp}
1056template<class T>
1057struct function {
1058    template<class CT>
1059    function(ffrt_function_header_t h, CT&& c) : header(h), closure(std::forward<CT>(c)) {}
1060    ffrt_function_header_t header;
1061    T closure;
1062};
1063
1064template<class T>
1065void exec_function_wrapper(void* t)
1066{
1067    auto f = (function<std::decay_t<T>>*)t;
1068    f->closure();
1069}
1070
1071template<class T>
1072void destroy_function_wrapper(void* t)
1073{
1074    auto f = (function<std::decay_t<T>>*)t;
1075    f->closure = nullptr;
1076}
1077
1078template<class T>
1079inline ffrt_function_header_t* create_function_wrapper(T&& func)
1080{
1081    using function_type = function<std::decay_t<T>>;
1082    static_assert(sizeof(function_type) <= ffrt_auto_managed_function_storage_size,
1083        "size of function must be less than ffrt_auto_managed_function_storage_size");
1084
1085    auto p = ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general);
1086    auto f = new (p) function_type(
1087        {exec_function_wrapper<T>, destroy_function_wrapper<T>},
1088        std::forward<T>(func));
1089    return (ffrt_function_header_t*)f;
1090}
1091
1092static inline void submit(std::function<void()>&& func)
1093{
1094    return ffrt_submit_base(create_function_wrapper(std::move(func)), NULL, NULL, NULL);
1095}
1096```
1097
1098### ffrt_wait
1099
1100<hr/>
1101* 同步等待,与ffrt_submit 配合使用
1102* 等待指定的数据被生产完成,或等待当前任务的所有子任务完成,在不满足条件之前,当前的执行上下文被suspend,在满足条件后恢复执行
1103
1104#### 声明
1105
1106```{.cpp}
1107void ffrt_wait_deps(ffrt_deps_t* deps);
1108void ffrt_wait();
1109```
1110
1111#### 参数
1112
1113`deps`
1114
1115* 需要等待被生产完成的数据的虚拟地址,这些地址可能作为某些任务在submit 时的out_deps,该依赖的生成见ffrt_deps_t章节,空指针表示无依赖
1116
1117#### 返回值
1118
1119* 不涉及
1120
1121#### 描述
1122* ffrt_wait_deps(deps) 用于等待deps指代的数据被生产完成才能执行后面的代码
1123* ffrt_wait() 用于等待当前上下文提交的所有子任务(`注意:不包括孙任务和下级子任务`)都完成才能执行后面的代码
1124* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用
1125* 在FFRT task 外部调用的wait 是OS 能够感知的等待,相对于FFRT task 内部调用的wait 是更加昂贵的,因此我们希望尽可能让更多的wait 发生在FFRT task 内部 ,而不是FFRT task 外部
1126
1127#### 样例
1128
1129**recursive fibonacci**
1130
1131串行版的fibonacci 可以实现为:
1132
1133```{.c}
1134#include <stdio.h>
1135
1136void fib(int x, int* y) {
1137    if (x <= 1) {
1138        *y = x;
1139    } else {
1140        int y1, y2;
1141        fib(x - 1, &y1);
1142        fib(x - 2, &y2);
1143        *y = y1 + y2;
1144    }
1145}
1146int main(int narg, char** argv)
1147{
1148    int r;
1149    fib(10, &r);
1150    printf("fibonacci 10: %d\n", r);
1151    return 0;
1152}
1153```
1154
1155若要使用 FFRT 实现并行(注,对于单纯的fibonacci,单个 Task 的计算量极小,不具有并行加速的意义,但这种调用pattern 对并行编程模型的灵活性考验是非常高的),其中1种可行的实现为:
1156
1157```{.c}
1158#include <stdio.h>
1159#include "ffrt.h"
1160
1161typedef struct {
1162    int x;
1163    int* y;
1164} fib_ffrt_s;
1165
1166typedef struct {
1167    ffrt_function_header_t header;
1168    ffrt_function_t func;
1169    ffrt_function_t after_func;
1170    void* arg;
1171} c_function;
1172
1173static void ffrt_exec_function_wrapper(void* t)
1174{
1175    c_function* f = (c_function*)t;
1176    if (f->func) {
1177        f->func(f->arg);
1178    }
1179}
1180
1181static void ffrt_destroy_function_wrapper(void* t)
1182{
1183    c_function* f = (c_function*)t;
1184    if (f->after_func) {
1185        f->after_func(f->arg);
1186    }
1187}
1188
1189#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1])
1190static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func,
1191    const ffrt_function_t after_func, void* arg)
1192{
1193    FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size,
1194        size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size);
1195    c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general);
1196    f->header.exec = ffrt_exec_function_wrapper;
1197    f->header.destroy = ffrt_destroy_function_wrapper;
1198    f->func = func;
1199    f->after_func = after_func;
1200    f->arg = arg;
1201    return (ffrt_function_header_t*)f;
1202}
1203
1204static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func,
1205    void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr)
1206{
1207    ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr);
1208}
1209
1210#define ffrt_deps_define(name, dep1, ...) const void* __v_##name[] = {dep1, ##__VA_ARGS__}; \
1211    ffrt_deps_t name = {sizeof(__v_##name) / sizeof(void*), __v_##name}
1212
1213void fib_ffrt(void* arg)
1214{
1215    fib_ffrt_s* p = (fib_ffrt_s*)arg;
1216    int x = p->x;
1217    int* y = p->y;
1218
1219    if (x <= 1) {
1220        *y = x;
1221    } else {
1222        int y1, y2;
1223        fib_ffrt_s s1 = {x - 1, &y1};
1224        fib_ffrt_s s2 = {x - 2, &y2};
1225        ffrt_deps_define(dx, &x);
1226        ffrt_deps_define(dy1, &y1);
1227        ffrt_deps_define(dy2, &y2);
1228        ffrt_deps_define(dy12, &y1, &y2);
1229        ffrt_submit_c(fib_ffrt, NULL, &s1, &dx, &dy1, NULL);
1230        ffrt_submit_c(fib_ffrt, NULL, &s2, &dx, &dy2, NULL);
1231        ffrt_wait_deps(&dy12);
1232        *y = y1 + y2;
1233    }
1234}
1235
1236int main(int narg, char** argv)
1237{
1238    int r;
1239    fib_ffrt_s s = {10, &r};
1240    ffrt_deps_define(dr, &r);
1241    ffrt_submit_c(fib_ffrt, NULL, &s, NULL, &dr, NULL);
1242    ffrt_wait_deps(&dr);
1243    printf("fibonacci 10: %d\n", r);
1244    return 0;
1245}
1246```
1247
1248`解析`:
1249
12501)   将fibonacci (x-1)和fibonacci (x-2) 作为2个Task 提交给FFRT,在两个Task 完成之后将结果累加;
1251
12522)   虽然单个Task 只能拆分成2个SubTask 但是子Task 可以继续拆分,因此,整个计算图的并行度是非常高的,Task 之间在FFRT 内部形成了一颗调用树;
1253
1254<img src="images/image-20220926152331554.png" style="zoom:100%" />
1255
1256> 以上实现,逻辑上虽与C++ API中的实现类似,但是用户显式管理数据生命周期和函数入参打包两个因素将使代码异常复杂
1257
1258
1259
1260### ffrt_deps_t
1261
1262* C API中对依赖数组的抽象,逻辑上等同于C++ API中的`std::vector<void*>`
1263
1264#### 声明
1265
1266```{.cpp}
1267typedef struct {
1268    uint32_t len;
1269    const void* const * items;
1270} ffrt_deps_t;
1271```
1272
1273#### 参数
1274
1275`len`
1276
1277* 所依赖的Signature的个数,取值大于等于0
1278
1279`item`
1280
1281* len个Signature的起始地址指针
1282
1283#### 返回值
1284
1285* 不涉及
1286
1287#### 描述
1288
1289* item为len个Signature的起始指针,该指针可以指向堆空间,也可以指向栈空间,但是要求分配的空间大于等于len * sizeof(void*)
1290
1291#### 样例
1292
1293* item指向栈空间的ffrt_deps_t
1294
1295```{.c}
1296#include "ffrt.h"
1297
1298int main(int narg, char** argv)
1299{
1300    int x1 = 1;
1301    int x2 = 2;
1302
1303    void *t[] = {&x1, &x2};
1304    ffrt_deps_t deps = {2, (const void* const *)&t};
1305    // some code use deps
1306    return 0;
1307}
1308```
1309
1310* item指向栈空间的ffrt_deps_t
1311
1312```{.c}
1313#include <stdlib.h>
1314#include "ffrt.h"
1315
1316int main(int narg, char** argv)
1317{
1318    int x1 = 1;
1319    int x2 = 2;
1320
1321    void** t = (void**)malloc(sizeof(void*) * 2);
1322    t[0]= &x1;
1323    t[1]= &x2;
1324    ffrt_deps_t deps = {2, t};
1325
1326    // some code use deps
1327    free(t);
1328    return 0;
1329}
1330```
1331
1332### ffrt_task_attr_t
1333
1334<hr/>
1335* 定义task 的属性的辅助类,与ffrt_submit 配合使用
1336
1337#### 声明
1338
1339```{.c}
1340typedef enum {
1341    ffrt_qos_inherent = -1,
1342    ffrt_qos_background,
1343    ffrt_qos_utility,
1344    ffrt_qos_default,
1345    ffrt_qos_user_initiated,
1346} ffrt_qos_t;
1347
1348typedef struct {
1349    char storage[ffrt_task_attr_storage_size];
1350} ffrt_task_attr_t;
1351typedef void* ffrt_task_handle_t;
1352
1353int ffrt_task_attr_init(ffrt_task_attr_t* attr);
1354void ffrt_task_attr_destroy(ffrt_task_attr_t* attr);
1355void ffrt_task_attr_set_qos(ffrt_task_attr_t* attr, ffrt_qos_t qos);
1356ffrt_qos_t ffrt_task_attr_get_qos(const ffrt_task_attr_t* attr);
1357void ffrt_task_attr_set_name(ffrt_task_attr_t* attr, const char* name);
1358const char* ffrt_task_attr_get_name(const ffrt_task_attr_t* attr);
1359```
1360
1361#### 参数
1362
1363`attr`
1364
1365* 创建的tasks属性的句柄
1366
1367`qos`
1368
1369* qos 设定的枚举类型
1370* inherent 是一个qos 设定策略,代表即将ffrt_submit 的task 的qos 继承当前task 的qos
1371
1372#### 返回值
1373
1374* 不涉及
1375
1376#### 描述
1377* `attr`所传递的内容会在ffrt_submit内部完成取存,ffrt_submit返回后用户即可销毁
1378* 约定
1379  * 在submit 时,如果不通过task_attr 设定qos,那么默认该提交的task的qos 为`ffrt_qos_default`
1380  * 在submit 时,如果通过task_attr 设定qos 为`ffrt_qos_inherent`,表示将该提交的task 的qos 与当前task 的qos 相同,在FFRT task 外部提交的属性为`ffrt_qos_inherent` 的task,其qos 为`ffrt_qos_default`
1381  * 其他情况下,该提交的task 的qos 被设定为指定的值
1382* 在`ffrt_task_attr_destroy`之后再对task_attr进行访问,其行为是未定义的
1383
1384#### 样例
1385
1386```{.c}
1387#include <stdio.h>
1388#include "ffrt.h"
1389
1390void my_print(void* arg)
1391{
1392    printf("hello ffrt\n");
1393}
1394
1395typedef struct {
1396    ffrt_function_header_t header;
1397    ffrt_function_t func;
1398    ffrt_function_t after_func;
1399    void* arg;
1400} c_function;
1401
1402static void ffrt_exec_function_wrapper(void* t)
1403{
1404    c_function* f = (c_function*)t;
1405    if (f->func) {
1406        f->func(f->arg);
1407    }
1408}
1409
1410static void ffrt_destroy_function_wrapper(void* t)
1411{
1412    c_function* f = (c_function*)t;
1413    if (f->after_func) {
1414        f->after_func(f->arg);
1415    }
1416}
1417
1418#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1])
1419static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func,
1420    const ffrt_function_t after_func, void* arg)
1421{
1422    FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size,
1423        size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size);
1424    c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general);
1425    f->header.exec = ffrt_exec_function_wrapper;
1426    f->header.destroy = ffrt_destroy_function_wrapper;
1427    f->func = func;
1428    f->after_func = after_func;
1429    f->arg = arg;
1430    return (ffrt_function_header_t*)f;
1431}
1432
1433static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func,
1434    void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr)
1435{
1436    ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr);
1437}
1438
1439int main(int narg, char** argv)
1440{
1441    ffrt_task_attr_t attr;
1442    ffrt_task_attr_init(&attr);
1443    ffrt_task_attr_set_qos(&attr, ffrt_qos_background);
1444    ffrt_submit_c(my_print, NULL, NULL, NULL, NULL, &attr);
1445    ffrt_task_attr_destroy(&attr);
1446    ffrt_wait();
1447    return 0;
1448}
1449```
1450
1451* 提交一个qos 级别为background 的任务
1452
1453
1454
1455### ffrt_submit_h
1456
1457<hr/>
1458
1459* 向调度器提交一个task,与ffrt_submit 的差别在于返回task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步
1460
1461#### 声明
1462
1463```{.cpp}
1464typedef void* ffrt_task_handle_t;
1465
1466ffrt_task_handle_t ffrt_submit_h(ffrt_function_t func, void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr);
1467void ffrt_task_handle_destroy(ffrt_task_handle_t handle);
1468```
1469
1470#### 参数
1471
1472`func`
1473
1474* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义
1475
1476`in_deps`
1477
1478* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义
1479
1480`out_deps`
1481
1482* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义
1483
1484`attr`
1485
1486* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义
1487
1488#### 返回值
1489
1490* task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步
1491
1492#### 描述
1493
1494* C API中的ffrt_task_handle_t的使用与C++ API中的ffrt::task_handle相同
1495* **差异在于:C API中的ffrt_task_handle_t需要用户调用`ffrt_task_handle_destroy`显式销毁,而C++ API无需该操作**
1496
1497
1498#### 样例
1499
1500```{.c}
1501#include <stdio.h>
1502#include "ffrt.h"
1503
1504void func0(void* arg)
1505{
1506    printf("hello ");
1507}
1508
1509void func1(void* arg)
1510{
1511    (*(int*)arg)++;
1512}
1513
1514void func2(void* arg)
1515{
1516    printf("world, x = %d\n", *(int*)arg);
1517}
1518
1519void func3(void* arg)
1520{
1521    printf("handle wait");
1522    (*(int*)arg)++;
1523}
1524
1525typedef struct {
1526    ffrt_function_header_t header;
1527    ffrt_function_t func;
1528    ffrt_function_t after_func;
1529    void* arg;
1530} c_function;
1531
1532static void ffrt_exec_function_wrapper(void* t)
1533{
1534    c_function* f = (c_function*)t;
1535    if (f->func) {
1536        f->func(f->arg);
1537    }
1538}
1539
1540static void ffrt_destroy_function_wrapper(void* t)
1541{
1542    c_function* f = (c_function*)t;
1543    if (f->after_func) {
1544        f->after_func(f->arg);
1545    }
1546}
1547
1548#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1])
1549static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func,
1550    const ffrt_function_t after_func, void* arg)
1551{
1552    FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size,
1553        size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size);
1554    c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general);
1555    f->header.exec = ffrt_exec_function_wrapper;
1556    f->header.destroy = ffrt_destroy_function_wrapper;
1557    f->func = func;
1558    f->after_func = after_func;
1559    f->arg = arg;
1560    return (ffrt_function_header_t*)f;
1561}
1562
1563static inline ffrt_task_handle_t ffrt_submit_h_c(ffrt_function_t func, const ffrt_function_t after_func,
1564    void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr)
1565{
1566    return ffrt_submit_h_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr);
1567}
1568
1569static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func,
1570    void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr)
1571{
1572    ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr);
1573}
1574
1575#define ffrt_deps_define(name, dep1, ...) const void* __v_##name[] = {dep1, ##__VA_ARGS__}; \
1576    ffrt_deps_t name = {sizeof(__v_##name) / sizeof(void*), __v_##name}
1577
1578int main(int narg, char** argv)
1579{
1580    // handle work with submit
1581    ffrt_task_handle_t h = ffrt_submit_h_c(func0, NULL, NULL, NULL, NULL, NULL); // not need some data in this task
1582    int x = 1;
1583    ffrt_deps_define(d1, &x);
1584    ffrt_deps_define(d2, &x, h);
1585    ffrt_submit_c(func1, NULL, &x, NULL, &d1, NULL);
1586    ffrt_submit_c(func2, NULL, &x, &d2, NULL, NULL); // this task depend x and h
1587    ffrt_task_handle_destroy(h);
1588
1589    // handle work with wait
1590    ffrt_task_handle_t h2 = ffrt_submit_h_c(func3, NULL, &x, NULL, NULL, NULL);
1591    ffrt_deps_define(d3, h2);
1592    ffrt_wait_deps(&d3);
1593    ffrt_task_handle_destroy(h2);
1594    printf("x = %d", x);
1595    ffrt_wait();
1596    return 0;
1597}
1598```
1599
1600* 预期的输出为
1601
1602```
1603hello world, x = 2
1604handle wait
1605x = 3
1606```
1607
1608
1609
1610### ffrt_this_task_get_id
1611
1612<hr/>
1613
1614* 返回当前task的id标识,更多使用用于维测(原因是task name可能重名)
1615
1616#### 声明
1617
1618```{.c}
1619uint64_t ffrt_this_task_get_id();
1620```
1621
1622#### 参数
1623
1624* 不涉及
1625
1626#### 返回值
1627
1628* 当前task的id
1629
1630#### 描述
1631
1632* 该接口在task内部调用将返回当前task的id标识,在task外部调用将返回0
1633* 可以基于该接口在task外部调用返回0的特性来区分函数是运行在FFRT 工作线程上还是非FFRT工作线程上
1634* task id为从1开始编码,每提交一个task便增加1,被设计成64bit,即便是每秒百万次提交,也需要292471.2年才会发生翻转
1635
1636#### 样例
1637
1638* 忽略
1639
1640
1641
1642### ffrt_this_task_update_qos
1643
1644<hr/>
1645
1646* 更新当前正在执行的task的优先级
1647
1648#### 声明
1649
1650```{.cpp}
1651int ffrt_this_task_update_qos(ffrt_qos_t qos);
1652```
1653
1654#### 参数
1655
1656* `qos` 新的优先级
1657
1658#### 返回值
1659
1660* 0表示成功,非0表示失败
1661
1662#### 描述
1663
1664* 该接口对当前task的qos调整会立即生效
1665* 如果新设定的qos与当前的qos不一致,则会block当前task的执行,再按照新的qos恢复执行
1666* 如果新设定的qos与当前的qos一致,则接口会立即返回0,不做任何处理
1667* **如果在非task内部调用该接口,则返回非0值,用户可以选择忽略或其他处理**
1668
1669#### 样例
1670
1671* 忽略
1672
1673
1674
1675## 同步原语
1676
1677### ffrt_mutex_t
1678<hr/>
1679* FFRT提供的类似pthread mutex 的性能实现
1680
1681#### 声明
1682
1683```{.cpp}
1684typedef enum {
1685    ffrt_error = -1,
1686    ffrt_success = 0,
1687    ffrt_error_nomem = ENOMEM,
1688    ffrt_error_timedout = ETIMEDOUT,
1689    ffrt_error_busy = EBUSY,
1690    ffrt_error_inval = EINVAL
1691} ffrt_error_t;
1692
1693struct ffrt_mutex_t;
1694
1695int ffrt_mutex_init(ffrt_mutex_t* mutex, const ffrt_mutexattr_t* attr);
1696int ffrt_mutex_lock(ffrt_mutex_t* mutex);
1697int ffrt_mutex_unlock(ffrt_mutex_t* mutex);
1698int ffrt_mutex_trylock(ffrt_mutex_t* mutex);
1699int ffrt_mutex_destroy(ffrt_mutex_t* mutex);
1700```
1701
1702#### 参数
1703
1704`attr`
1705
1706* 当前FFRT只支持基础类型的mutex,因此attr必须为空指针
1707
1708`mutex`
1709
1710* 指向所操作的互斥锁的指针
1711
1712#### 返回值
1713
1714* 若成功则为 ffrt_success ,否则发生错误
1715
1716#### 描述
1717* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为
1718* 该功能能够避免pthread传统的pthread_mutex_t 在抢不到锁时陷入内核的问题,在使用得当的条件下将会有更好的性能
1719* **注意:目前暂不支持递归和定时功能**
1720* **注意:C API中的ffrt_mutex_t需要用户调用`ffrt_mutex_init`和`ffrt_mutex_destroy`显式创建和销毁,而C++ API无需该操作**
1721
1722#### 样例
1723
1724```{.c}
1725#include <stdio.h>
1726#include "ffrt.h"
1727
1728typedef struct {
1729    int* sum;
1730    ffrt_mutex_t* mtx;
1731} tuple;
1732
1733void func(void* arg)
1734{
1735    tuple* t = (tuple*)arg;
1736
1737    int ret = ffrt_mutex_lock(t->mtx);
1738    if (ret != ffrt_success) {
1739        printf("error\n");
1740    }
1741    (*t->sum)++;
1742    ret = ffrt_mutex_unlock(t->mtx);
1743    if (ret != ffrt_success) {
1744        printf("error\n");
1745    }
1746}
1747
1748typedef struct {
1749    ffrt_function_header_t header;
1750    ffrt_function_t func;
1751    ffrt_function_t after_func;
1752    void* arg;
1753} c_function;
1754
1755static void ffrt_exec_function_wrapper(void* t)
1756{
1757    c_function* f = (c_function*)t;
1758    if (f->func) {
1759        f->func(f->arg);
1760    }
1761}
1762
1763static void ffrt_destroy_function_wrapper(void* t)
1764{
1765    c_function* f = (c_function*)t;
1766    if (f->after_func) {
1767        f->after_func(f->arg);
1768    }
1769}
1770
1771#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1])
1772static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func,
1773    const ffrt_function_t after_func, void* arg)
1774{
1775    FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size,
1776        size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size);
1777    c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general);
1778    f->header.exec = ffrt_exec_function_wrapper;
1779    f->header.destroy = ffrt_destroy_function_wrapper;
1780    f->func = func;
1781    f->after_func = after_func;
1782    f->arg = arg;
1783    return (ffrt_function_header_t*)f;
1784}
1785
1786static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func,
1787    void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr)
1788{
1789    ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr);
1790}
1791
1792void ffrt_mutex_task()
1793{
1794    int sum = 0;
1795    ffrt_mutex_t mtx;
1796    tuple t = {&sum, &mtx};
1797    int ret = ffrt_mutex_init(&mtx, NULL);
1798    if (ret != ffrt_success) {
1799        printf("error\n");
1800    }
1801    for (int i = 0; i < 10; i++) {
1802        ffrt_submit_c(func, NULL, &t, NULL, NULL, NULL);
1803    }
1804    ffrt_mutex_destroy(&mtx);
1805    ffrt_wait();
1806    printf("sum = %d", sum);
1807}
1808
1809int main(int narg, char** argv)
1810{
1811    int r;
1812    ffrt_submit_c(ffrt_mutex_task, NULL, NULL, NULL, NULL, NULL);
1813    ffrt_wait();
1814    return 0;
1815}
1816```
1817
1818预期输出为
1819
1820```
1821sum=10
1822```
1823
1824* 该例子为功能示例,实际中并不鼓励这样使用
1825
1826
1827### ffrt_cond_t
1828<hr/>
1829
1830* FFRT提供的类似pthread 信号量的性能实现
1831
1832#### 声明
1833
1834```{.c}
1835typedef enum {
1836    ffrt_error = -1,
1837    ffrt_success = 0,
1838    ffrt_error_nomem = ENOMEM,
1839    ffrt_error_timedout = ETIMEDOUT,
1840    ffrt_error_busy = EBUSY,
1841    ffrt_error_inval = EINVAL
1842} ffrt_error_t;
1843
1844struct ffrt_cond_t;
1845typedef enum {
1846    ffrt_clock_realtime = CLOCK_REALTIME,
1847    ffrt_clock_monotonic = CLOCK_MONOTONIC
1848} ffrt_clockid_t;
1849
1850int ffrt_condattr_init(ffrt_condattr_t* attr);
1851int ffrt_condattr_destroy(ffrt_condattr_t* attr);
1852int ffrt_condattr_setclock(ffrt_condattr_t* attr, ffrt_clockid_t clock);
1853int ffrt_condattr_getclock(const ffrt_condattr_t* attr, ffrt_clockid_t* clock);
1854
1855int ffrt_cond_init(ffrt_cond_t* cond, const ffrt_condattr_t* attr);
1856int ffrt_cond_signal(ffrt_cond_t* cond);
1857int ffrt_cond_broadcast(ffrt_cond_t* cond);
1858int ffrt_cond_wait(ffrt_cond_t*cond, ffrt_mutex_t* mutex);
1859int ffrt_cond_timedwait(ffrt_cond_t* cond, ffrt_mutex_t* mutex, const struct timespec* time_point);
1860int ffrt_cond_destroy(ffrt_cond_t* cond);
1861```
1862
1863#### 参数
1864
1865`cond`
1866
1867* 指向所操作的信号量的指针
1868
1869`attr`
1870
1871* 属性设定,空指针表示使用默认属性
1872
1873`mutex`
1874
1875* 指向要在阻塞期间解锁的互斥锁的指针
1876
1877`time_point`
1878
1879* 指向指定等待时限时间的对象的指针
1880
1881
1882#### 返回值
1883
1884* 若成功则为 ffrt_success,若在锁定互斥前抵达时限则为 ffrt_error_timedout
1885
1886#### 描述
1887* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为
1888* 该功能能够避免传统的pthread_cond_t在条件不满足时陷入内核的问题,在使用得当的条件下将会有更好的性能
1889* **注意:C API中的ffrt_cond_t需要用户调用`ffrt_cond_init`和`ffrt_cond_destroy`显式创建和销毁,而C++ API中依赖构造和析构自动完成**
1890
1891#### 样例
1892
1893```{.c}
1894#include <stdio.h>
1895#include "ffrt.h"
1896
1897typedef struct {
1898    ffrt_cond_t* cond;
1899    int* a;
1900    ffrt_mutex_t* lock_;
1901} tuple;
1902
1903void func1(void* arg)
1904{
1905    tuple* t = (tuple*)arg;
1906    int ret = ffrt_mutex_lock(t->lock_);
1907    if (ret != ffrt_success) {
1908        printf("error\n");
1909    }
1910    while (*t->a != 1) {
1911        ret = ffrt_cond_wait(t->cond, t->lock_);
1912        if (ret != ffrt_success) {
1913            printf("error\n");
1914        }
1915    }
1916    ret = ffrt_mutex_unlock(t->lock_);
1917    if (ret != ffrt_success) {
1918        printf("error\n");
1919    }
1920    printf("a = %d", *(t->a));
1921}
1922
1923void func2(void* arg)
1924{
1925    tuple* t = (tuple*)arg;
1926    int ret = ffrt_mutex_lock(t->lock_);
1927    if (ret != ffrt_success) {
1928        printf("error\n");
1929    }
1930    *(t->a) = 1;
1931    ret = ffrt_cond_signal(t->cond);
1932    if (ret != ffrt_success) {
1933        printf("error\n");
1934    }
1935    ret = ffrt_mutex_unlock(t->lock_);
1936    if (ret != ffrt_success) {
1937        printf("error\n");
1938    }
1939}
1940
1941typedef struct {
1942    ffrt_function_header_t header;
1943    ffrt_function_t func;
1944    ffrt_function_t after_func;
1945    void* arg;
1946} c_function;
1947
1948static void ffrt_exec_function_wrapper(void* t)
1949{
1950    c_function* f = (c_function*)t;
1951    if (f->func) {
1952        f->func(f->arg);
1953    }
1954}
1955
1956static void ffrt_destroy_function_wrapper(void* t)
1957{
1958    c_function* f = (c_function*)t;
1959    if (f->after_func) {
1960        f->after_func(f->arg);
1961    }
1962}
1963
1964#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1])
1965static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func,
1966    const ffrt_function_t after_func, void* arg)
1967{
1968    FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size,
1969        size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size);
1970    c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general);
1971    f->header.exec = ffrt_exec_function_wrapper;
1972    f->header.destroy = ffrt_destroy_function_wrapper;
1973    f->func = func;
1974    f->after_func = after_func;
1975    f->arg = arg;
1976    return (ffrt_function_header_t*)f;
1977}
1978
1979static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func,
1980    void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr)
1981{
1982    ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr);
1983}
1984
1985void ffrt_cv_task()
1986{
1987    ffrt_cond_t cond;
1988    int ret = ffrt_cond_init(&cond, NULL);
1989    if (ret != ffrt_success) {
1990        printf("error\n");
1991    }
1992    int a = 0;
1993    ffrt_mutex_t lock_;
1994    tuple t = {&cond, &a, &lock_};
1995    ret = ffrt_mutex_init(&lock_, NULL);
1996    if (ret != ffrt_success) {
1997        printf("error\n");
1998    }
1999    ffrt_submit_c(func1, NULL, &t, NULL, NULL, NULL);
2000    ffrt_submit_c(func2, NULL, &t, NULL, NULL, NULL);
2001    ffrt_wait();
2002    ffrt_cond_destroy(&cond);
2003    ffrt_mutex_destroy(&lock_);
2004}
2005
2006int main(int narg, char** argv)
2007{
2008    ffrt_submit_c(ffrt_cv_task, NULL, NULL, NULL, NULL, NULL);
2009    ffrt_wait();
2010    return 0;
2011}
2012```
2013
2014预期输出为:
2015
2016```
2017a=1
2018```
2019
2020* 该例子为功能示例,实际中并不鼓励这样使用
2021
2022## 杂项
2023
2024### ffrt_usleep
2025
2026<hr/>
2027* FFRT提供的类似C11 sleep和linux usleep的性能实现
2028
2029#### 声明
2030
2031```{.c}
2032int ffrt_usleep(uint64_t usec);
2033```
2034
2035#### 参数
2036
2037`usec`
2038
2039* 睡眠的us数
2040
2041#### 返回值
2042
2043* 不涉及
2044
2045#### 描述
2046* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为
2047* 该功能能够避免传统的sleep 睡眠时陷入内核的问题,在使用得当的条件下将会有更好的性能
2048
2049#### 样例
2050
2051```{.c}
2052#include <time.h>
2053#include <stdio.h>
2054#include "ffrt.h"
2055
2056void func(void* arg)
2057{
2058    printf("Time: %s", ctime(&(time_t){time(NULL)}));
2059    ffrt_usleep(2000000); // 睡眠 2 秒
2060    printf("Time: %s", ctime(&(time_t){time(NULL)}));
2061}
2062
2063typedef struct {
2064    ffrt_function_header_t header;
2065    ffrt_function_t func;
2066    ffrt_function_t after_func;
2067    void* arg;
2068} c_function;
2069
2070static void ffrt_exec_function_wrapper(void* t)
2071{
2072    c_function* f = (c_function*)t;
2073    if (f->func) {
2074        f->func(f->arg);
2075    }
2076}
2077
2078static void ffrt_destroy_function_wrapper(void* t)
2079{
2080    c_function* f = (c_function*)t;
2081    if (f->after_func) {
2082        f->after_func(f->arg);
2083    }
2084}
2085
2086#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1])
2087static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func,
2088    const ffrt_function_t after_func, void* arg)
2089{
2090    FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size,
2091        size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size);
2092    c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general);
2093    f->header.exec = ffrt_exec_function_wrapper;
2094    f->header.destroy = ffrt_destroy_function_wrapper;
2095    f->func = func;
2096    f->after_func = after_func;
2097    f->arg = arg;
2098    return (ffrt_function_header_t*)f;
2099}
2100
2101static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func,
2102    void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr)
2103{
2104    ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr);
2105}
2106
2107int main(int narg, char** argv)
2108{
2109    ffrt_submit_c(func, NULL, NULL, NULL, NULL, NULL);
2110    ffrt_wait();
2111    return 0;
2112}
2113```
2114
2115### ffrt_yield
2116<hr/>
2117* 当前task 主动让出CPU 执行资源,让其他可以被执行的task 先执行,如果没有其他可被执行的task,yield 无效
2118
2119#### 声明
2120
2121```{.cpp}
2122void ffrt_yield();
2123```
2124
2125#### 参数
2126
2127* 不涉及
2128
2129#### 返回值
2130
2131* 不涉及
2132
2133#### 描述
2134* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为
2135* 此函数的确切行为取决于实现,特别是使用中的FFRT 调度程序的机制和系统状态
2136
2137#### 样例
2138
2139* 省略
2140
2141
2142# 部署
2143
2144## 部署方式
2145<img src="images/image-20230120153923679.png" alt="image-20230120153923679" style="zoom:67%;" />
2146
2147* FFRT的部署依赖FFRT动态库libffrt.so和一组header头文件
2148
2149* FFRT的头文件为`ffrt.h`,内部包含了C++ API,C API和C base API
2150  * ffrt.h 定义为:
2151  ```{.cpp}
2152  #ifndef FFRT_API_FFRT_H
2153  #define FFRT_API_FFRT_H
2154  #ifdef __cplusplus
2155  #include "cpp/task.h"
2156  #include "cpp/deadline.h"
2157  #include "cpp/sys_event.h"
2158  #include "cpp/mutex.h"
2159  #include "cpp/condition_variable.h"
2160  #include "cpp/sleep.h"
2161  #include "cpp/thread.h"
2162  #include "cpp/config.h"
2163  #include "cpp/future.h"
2164  #else
2165  #include "c/task.h"
2166  #include "c/deadline.h"
2167  #include "c/sys_event.h"
2168  #include "c/mutex.h"
2169  #include "c/condition_variable.h"
2170  #include "c/sleep.h"
2171  #include "c/thread.h"
2172  #include "c/config.h"
2173  #endif
2174  #endif
2175  ```
2176  * C base API定义示例:
2177  ```{.cpp}
2178  void ffrt_submit_base(ffrt_function_header_t* func, ...);
2179  int ffrt_mutex_init(...);
2180  ```
2181  * C API定义示例:
2182  ```{.cpp}
2183  static inline void ffrt_submit(ffrt_function_t func, void* arg, ...)
2184  {
2185      ffrt_submit_base(ffrt_create_function_wrapper(func, arg), ...);
2186  }
2187  ```
2188  * C++ API定义示例:
2189  ```{.cpp}
2190  namespace ffrt {
2191  static inline void submit(std::function& func, ...)
2192  {
2193      ffrt_submit_base(ffrt_create_function_wrapper(func), ...);
2194  }
2195  struct mutex {
2196      mutex() {
2197          ffrt_mutex_init(...);
2198          ...
2199      };
2200  }
2201  ```
2202
2203* **出于易用性方面的考虑,除非必要,强烈建议你使用C++ API,调用C API将会使你的代码非常臃肿或者更容易产生资源未释放问题**
2204
2205| 需求列表                                                     |
2206| ------------------------------------------------------------ |
2207| 需求1:ABI兼容性,在NDK场景中由于用户的编译环境与FFRT的编译环境不同,使用C++接口可能存在ABI兼容性问题,要有解决方案 |
2208| 需求2:用户的编译环境为纯C编译环境,不想因为引入FFRT而引入C++元素的场景,要有解决方案 |
2209| 需求3:易用性,尽可能让接口简单易用,用户少出错              |
2210
2211* 对于需求1,通过在用户调用的C++接口和FFRT的实现之间增加一个C base API层,并基于头文件方式将API中的C++的元素编译到用户的so,从而解决ABI兼容的问题
2212* 对于需求2,可以通过C Base API解决
2213* 对于需求3,建议用户尽可能使用C++ API,以避免C API固有的资源未初始化/释放、参数冗长等问题,对于不得不使用C API的场景,FFRT仍然支持用户使用C API和C base API
2214
2215
2216
2217<br/>
2218<br/>
2219
2220<hr/>
2221# 实战指南
2222
2223## 步骤1: 分析应用
2224
2225使用 FFRT 并行编程的第一步便是您需要了解你的应用。
2226
2227【建议1】:使用 Task 梳理应用的流程。
2228
2229使用 Task 梳理应用的流程,并且尽可能使用数据来表达 Task 之间的依赖。当然如果两个 Task 之间如无数据依赖,仅存在控制依赖,您也可以创建一个虚拟的(或者逻辑上的)数据依赖。
2230
2231<img src="images/image-20220926152831526.png" style="zoom:70%" />
2232
2233<center>AIRAW 的数据流图</center>
2234
2235基于数据流图,可以很容易判定出哪些 Task 是可以并发的,比如,Slice0 的 NPU Task 和 Slice1 的 GPU Pre Task 是可以并发的,因为它们没有任何依赖。
2236
2237反过来,如果并发的效果不理想,也可以通过调整数据流图来优化并发。例如,假如上图中GPU Pre Task 执行时间有很大波动,但平均耗时略小于 NPU Task,会出现某些时刻 GPU Pre Task 拖慢整个执行时间。此时,如果将 GPU Pre Task 的输出 Buffer 改成3个(或者更多)的 Buffer ,可以增加 GPU Pre Task 和 NPU Task 的并发机会,将降低波动对总执行时间的影响。
2238
2239
2240
2241【建议2】:这里不用太担心 Task 大或小的问题,因为 FFRT 允许你在 Task 内部继续拆分 SubTask,可以逐步细化。
2242
2243下图中,第一次画数据流图时,可以不将 FaceDirection 和 UpdateExistFaceImageInfo 两个 Task 展开,可以逐步细化。
2244
2245<img src="images/image-20220926153003884.png" style="zoom:70%" />
2246
2247<center>某拍照业务的数据流图</center>
2248
2249【建议3】:上述流程图或者数据流图不要求是静态图(即 Task 数量和 Task 依赖关系是固定的)
2250
2251FFRT 允许动态提交 Task ,在编程界面上不体现图的概念,FFRT 内部会根据Task 之间的依赖关系动态调整数据流图的节点。
2252
2253
2254【建议4】:尽可能对应用做热点分析
2255
2256如果是对存量代码的 FFRT 化改造,那么,使用 System Trace 这类工具能帮助您聚焦在性能热点上,比如下图可以很容易知道当前的性能Bound,在分析数据流图时,可以重点关注这些热点任务。
2257
2258<img src="images/image-20220926153030993.png" style="zoom:70%" />
2259
2260<center>某业务的System Trace</center>
2261
2262## 步骤2: 并行化应用
2263
2264【建议1】:不要直接使用线程,使用 FFRT 提交Task。
2265
2266如果应用中有明显的数据依赖关系,那么 FFRT 将会非常适合;最差的情况是应用没有数据依赖或难以并行(如果真的存在),您仍然可以把 FFRT 当做一个高效的进程级线程池、或者协程库去使用它,但非常不建议你继续创建线程。
2267
2268
2269
2270【建议2】:Task 最好被建模为纯函数。
2271
2272纯函数是指其执行没有副作用,例如更新全局数据结构。每个任务都依赖于其输入/输出签名来连接到其他任务。
2273
2274请注意,即使 Task 不是"纯"的,FFRT 仍然适用。只要任务使用的数据依赖或者锁足以保证正确执行,FFRT 就能正常工作。
2275
2276
2277
2278【建议3】:尽可能尝试通过 inDeps/outDeps 表达依赖,而不是使用 ffrt::wait()。
2279
2280这是因为 FFRT 跟踪和处理 inDeps/outDeps 比调用显式 ffrt::wait() 函数更自然、更便宜。
2281
2282
2283
2284【建议4】:注意 Task 粒度
2285
2286以适当的粒度提交任务至关重要:目前每个任务的调度开销约为 10 us。如果 Task 的粒度非常小,那么开销的百分比将会很高。FFRT 会继续基于软硬件的方式优化调度开销。
2287
2288
2289
2290【建议5】:尽可能使用 FFRT 原语
2291
2292如果需要mutex、sleep、异步 I/O,请使用 FFRT 原语,而不是使用OS 提供的版本。因为这些 FFRT 提供的实现在与 FFRT 配合时开销将会更小。
2293
2294
2295
2296【建议6】:在需要时,使用 ffrt::wait() 确保栈变量的生命周期。
2297
2298如果子任务使用驻留在父任务栈上的数据,则父任务应避免在子任务执行完成前返回。在父任务的末尾添加 ffrt::wait() 可以解决这个问题。
2299
2300
2301
2302## 步骤3: 优化应用
2303
2304【建议1】:基于System Trace,分析并行是否符合预期
2305
2306FFRT 已经内置 SysTrace 支持,默认以txx.xx表示,非常有利于分析 Task 粒度和并发度。未来,在性能分析和维测方面将继续增强。
2307
2308<img src="images/image-20220926153209875.png" style="zoom:70%" />
2309
2310【建议2】:对于耗时的 Task,尝试提交 SubTask,提升应用的并行度
2311
2312
2313【建议3】:在合适的场景,使用 Deadline 调度,实现能效和性能的平衡
2314
2315方案正在验证中,待更新。
2316
2317
2318
2319## 样例: CameraHal QuickThumb
2320
2321### 步骤1: 分析应用
2322
2323<img src="images/image-20220926153255824.png" style="zoom:70%" />
2324
23251)   QuickThumb 是 CameraHal 中实现的对一张图片进行缩小的功能,整体运行时间约30 us;
2326
23272)   在实现上分为两层循环,外层的一次循环输出1行,内层的1次循环输出该行的m列;
2328
23293)   在划分 Task 时,一种简单的做法是1行的处理就是1个Task。
2330
2331### 步骤2: 并行化应用
2332<img src="images/image-20220926153509205.png" style="zoom:100%" />
2333
2334 1)   根据纯函数的定义,该 Task 的输入输出的数据是非常之多的,因此,这个场景下使用更宽泛的纯函数的定义,只需要考虑在 Task 内部会被写,但是却被定义在 Task 外部的变量即可;
2335
23362)   按照上面的原则,将 py/puv 的定义移到 Task 内部可避免多个 Task 同时写 py/puv 的问题;
2337
23383)   s32r 的处理可以有两种方式,都能得到正确的功能:a. 保持定义在Task 外部,但作为Task 的输出依赖;b. 将s32r定义在Task 内部,作为Task的私有变量。显然,b 方案能够获得更好的性能
2339
2340
2341
2342### 步骤3: 优化应用
2343
2344通过System Trace,会发现上述改造方案的Task 粒度较小,大约单个Task 耗时在5us左右,因此,扩大Task的粒度为32行处理,得到最终的并行结果,下图为使用4小核和3中核的结果。
2345
2346<img src="images/image-20220926153603572.png" style="zoom:100%" />
2347
2348
2349
2350## 样例: Camera AIRAW
2351
2352### 步骤1: 分析应用
2353<img src="images/image-20220926153611121.png" style="zoom:70%" />
2354
2355AIRAW 的处理包括了3个处理步骤,在数据面上,可以按slice 进行切分,在不考虑pre_outbuf和npu_outbuf 在slice间复用的情况下,数据流图如上图所示。
2356
2357<img src="images/image-20220926152831526.png" style="zoom:70%" />
2358
2359为了节省运行过程中的内存占用,但不影响整体性能,可以只保留2个pre_outbuf和2个npu_outbuf。
2360
2361`为此付出的代价是:Buffer 的复用产生了slice3 的GPU Pre Task 依赖slice1 的NPU Task 完成,俗称反压,又称生产者依赖关系。但是,如果您使用 FFRT 来实现,将会是非常自然而高效的`
2362
2363### 步骤2: 并行化应用
2364
2365```{.cpp}
2366constexpr uint32_t SLICE_NUM = 24;
2367constexpr uint32_t BUFFER_NUM = 2;
2368
2369int input[SLICE_NUM]; // input is split into SLICE_NUM slices
2370int pre_outbuf[BUFFER_NUM]; // gpu pre task output buffers
2371int npu_outbuf[BUFFER_NUM]; // npu output buffers
2372int output[SLICE_NUM]; // output is split into SLICE_NUM slices
2373
2374for (uint32_t i = 0; i < SLICE_NUM; i++) {
2375  uint32_t buf_id = i % BUFFER_NUM;
2376  ffrt::submit(gpuPreTask, {input + i}, {pre_outbuf + buf_id});
2377  ffrt::submit(npuTask, {pre_outbuf + buf_id}, {npu_outbuf + buf_id});
2378  ffrt::submit(gpuPostTask, {npu_outbuf + buf_id}, {output + i});
2379}
2380
2381ffrt::wait();
2382```
2383
2384### 步骤3: 优化应用
2385
2386<img src="images/image-20220926153825527.png" style="zoom:100%" />
2387
2388基于以上实现,从上面的Trace中我们看到,NPU 的硬件时间被完全用满,系统端到端性能达到最优,而付出的开发代价将会比GCD 或多线程小的多。
2389
2390
2391
2392## 样例: Camera FaceStory
2393
2394### 步骤1: 分析应用
2395
2396<img src="images/image-20220926153003884.png" style="zoom:70%" />
2397
2398
2399
2400### 步骤2: 并行化应用
2401
2402<img src="images/image-20220926153906692.png" style="zoom:100%" />
2403
2404代码改造样例
2405
24061)     该场景输出存量代码迁移,只需将原先串行的代码以Task的方式提交即可;
2407
24082)     过程中需要考虑Data Race和数据生命周期;
2409
24103)     先提交大的Task,根据需要逐步拆分SubTask。
2411
2412
2413
2414### 步骤3: 优化应用
2415
2416<img src="images/image-20220926153030993.png" style="zoom:100%" />
2417
2418<center>原始System Trace</center>
2419
2420<img src="images/image-20220926153925963.png" style="zoom:100%" />
2421
2422
2423
2424
2425<center>改造后System Trace</center>
2426
2427并行化的收益来自于:
2428
24291)    多分支或循环并发,实现CPU前后处理和NPU的并发
2430
24312)    子任务拆分,进一步提升并行度
2432
24333)    基于数据流图优化CPU L2 Cache Flush频次
2434
24354)    NPU Worker Thread实时优先级调整,后续FFRT中考虑独立出XPU调度Worker来保证实时性
2436
24375)    在未来,模型加载使用FFRT submit,模型加载内部也可以使用submit来继续拆分,能够优化整个业务的启动耗时。
2438
2439<br/>
2440<br/>
2441<hr/>
2442
2443
2444
2445# 使用建议
2446
2447## 建议1: 函数化
2448
2449**基本思想:计算过程函数化**
2450
2451* 程序过程各步骤以函数封装表达,函数满足类纯函数特性
2452* 无全局数据访问
2453* 无内部状态保留
2454* 通过ffrt::submit()接口以异步任务方式提交函数执行
2455* 将函数访问的数据对象以及访问方式在ffrt::submit()接口中的in_deps/out_deps参数表达
2456* 程序员通过inDeps/outDeps参数表达任务间依赖关系以保证程序执行的正确性
2457
2458> 做到纯函数的好处在于:1. 能够最大化挖掘并行度,2.避免DataRace和锁的问题
2459
2460
2461
2462**在实际中,可以根据场景放松纯函数的约束,但前提是:**
2463
2464* 确定添加的in_deps/out_deps可确保程序正确执行
2465* 通过FFRT提供的锁机制保护对全局变量的访问
2466
2467
2468
2469## 建议2: 注意任务粒度
2470
2471* FFRT管理和调度异步任务执行有调度开销,任务粒度(执行时间)需匹配调度开销
2472* 大量小粒度任务造成FFRT调度开销占比增加,性能下降,解决方法:
2473  * 将多个小粒度任务聚合为大粒度任务一次发送给FFRT异步执行
2474  * 同步方式执行小粒度任务,不发送给FFRT异步执行。需注意和异步任务之间的数据同步问题,在需要同步的地方插入ffrt::wait()
2475  * 下面的例子中,fib_ffrt2会比fib_ffrt1拥有更好的性能
2476
2477  ```{.cpp}
2478  #include "ffrt.h"
2479  void fib_ffrt1(int x, int& y)
2480  {
2481      if (x <= 1) {
2482          y = x;
2483      } else {
2484          int y1, y2;
2485          ffrt::submit([&] {fib_ffrt1(x - 1, y1);}, {&x}, {&y1} );
2486          ffrt::submit([&] {fib_ffrt1(x - 2, y2);}, {&x}, {&y2} );
2487          ffrt::submit([&] {y = y1 + y2;}, {&y1, &y2}, {} );
2488          ffrt::wait();
2489      }
2490  }
2491
2492  void fib_ffrt2(int x, int& y)
2493  {
2494      if (x <= 1) {
2495          y = x;
2496      } else {
2497          int y1, y2;
2498          ffrt::submit([&] {fib_ffrt2(x - 1, y1);}, {&x}, {&y1} );
2499          ffrt::submit([&] {fib_ffrt2(x - 2, y2);}, {&x}, {&y2} );
2500          ffrt::wait({&y1, &y2});
2501          y = y1 + y2;
2502      }
2503  }
2504  ```
2505
2506
2507
2508## 建议3: 数据生命周期
2509
2510* FFRT的任务提交和执行是异步的,因此需要确保任务执行时,对任务中涉及的数据的访问是有效的
2511* 常见问题:子任务使用父任务栈数据,当父任务先于子任务执行完成时释放栈数据,子任务产生数据访问错误
2512* 解决方法1:父任务中增加ffrt::wait()等待子任务完成
2513
2514```{.cpp}
2515#include "ffrt.h"
2516void fib_ffrt(int x, int& y)
2517{
2518    if (x <= 1) {
2519        y = x;
2520    } else {
2521        int y1, y2;
2522        ffrt::submit([&] {fib_ffrt(x - 1, y1);}, {&x}, {&y1} );
2523        ffrt::submit([&] {fib_ffrt(x - 2, y2);}, {&x}, {&y2} );
2524        ffrt::submit([&] {y = y1 + y2;}, {&y1, &y2}, {} );
2525        ffrt::wait(); // 用于保证y1 y2的生命周期
2526    }
2527}
2528```
2529
2530* 解决方法2:将数据由栈移到堆,手动管理生命周期
2531
2532```{.cpp}
2533#include "ffrt.h"
2534void fib_ffrt(int x, int* y)
2535{
2536    if (x <= 1) {
2537        *y = x;
2538    } else {
2539        int *y1 = (int*)malloc(sizeof(int));
2540        int *y2 = (int*)malloc(sizeof(int));
2541
2542        ffrt::submit([=] {fib_ffrt(x - 1, y1);}, {}, {y1} );
2543        ffrt::submit([=] {fib_ffrt(x - 2, y2);}, {}, {y2} );
2544        ffrt::submit([=] {*y = *y1 + *y2; }, {y1, y2}, {} );
2545		ffrt::wait();
2546    }
2547}
2548```
2549
2550
2551
2552## 建议4: 使用FFRT提供的替代API
2553
2554* 禁止在FFRT任务中使用系统线程库API创建线程,使用submit提交任务
2555* 使用FFRT提供的锁,条件变量,睡眠,IO等API代替系统线程库API
2556  * 使用系统线程库API可能造成工作线程阻塞,引起额外性能开销
2557
2558
2559
2560## 建议5: Deadline机制
2561
2562* **必须用于具备周期/重复执行特征的处理流程**
2563* 在有明确时间约束和性能关键的处理流程中使用,避免滥用
2564* 在相对大颗粒度的处理流程中使用,例如具有16.6ms时间约束的帧处理流程
2565
2566
2567
2568## 建议6: 从线程模型迁移
2569
2570* 创建线程替代为创建FFRT任务
2571  * 线程从逻辑上类似无in_deps的任务
2572* 识别线程间的依赖关系,并将其表达在任务的依赖关系in_deps/out_deps2573* 线程内计算过程分解为异步任务调用
2574* 通过任务依赖关系和锁机制避免并发任务数据竞争问题
2575
2576
2577
2578# 已知限制
2579
2580## 不支持thread_local变量
2581
2582* Task内部创建或Task间传递的thread_local变量的行为都是不确定的
2583
2584* 原因在于FFRT在编程模型中已经没有thread的概念,只有task的概念
2585* 在C++的语义下,thread_local可以被正常编译,但是使用该thread_local变量的task在哪一个线程上执行时不确定的
2586* 对于使用了FFRT进程中的non-worker,thread_local的行为不受FFRT影响
2587
2588> 类似的,与thread绑定的thread_idx/pthread_specific/递归锁/线程优先级/线程亲和性/递归锁具有相似的问题
2589
2590`建议`
2591
2592* 避免使用这些特性,如必须使用,使用FFRT的task local来替代
2593
2594
2595## 以动态库方式部署FFRT
2596
2597* 只能以动态库方式部署FFRT,静态库部署可能有多实例问题,例如:当多个被同一进程加载的so都以静态库的方式使用FFRT时,FFRT会被实例化成多份,其行为是未知的,这也不是FFRT设计的初衷
2598
2599