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| V0.1.1 | shengxia | 部分描述更新 | 2023/08/24 | 14| V0.1.2 | wanghuachen | 新增串行队列相关接口以及说明,增加规范以避免double free问题 | 2023/10/07 | 15| V0.1.3 | shengxia | 优化串行队列内容描述 | 2024/01/12 | 16 17<br/> 18 19<hr/> 20# 缩写 21 22| 缩略语 | 英文全名 | 中文解释 | 23| ------------- | ------------------------------- | ------------------------------------------------------------ | 24| FFRT | Function Flow Run Time | 软件实现Function Flow运行时用于任务调度和执行 | 25| Function Flow | Function Flow Programming Model | Function Flow编程模型 | 26| Pure Function | Pure Function | 纯函数,注意本文中定义的纯函数指的是通过表达相互间数据依赖即可由调度系统保证正确执行的任务。 | 27 28 29<br/> 30<hr/> 31# 编程模型 32## 两种编程模型 33 34| | 线程编程模型 | FFRT任务编程模型 | 35| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 36| 并行度挖掘方式 | 程序员通过创建多线程并把任务分配到每个线程中执行来挖掘运行时的并行度 | 程序员(编译器工具或语言特性配合)静态编程时将应用分解成任务及其数据依赖关系,运行时调度器分配任务到工作线程执行 | 37| 谁负责线程创建 | 程序员负责创建线程,线程编程模型无法约束线程的创建,滥用可能造成系统中大量线程 | FFRT运行时负责工作线程池的创建和管理由调度器负责,程序员无法直接创建线程 | 38| 负载均衡 | 程序员静态编程时将任务映射到线程,映射不合理或任务执行时间不确定造成线程负载不均 | FFRT运行时根据线程执行状态调度就绪任务到空闲线程执行,减轻了线程负载不均问题 | 39| 调度开销 | 线程调度由内核态调度器完成,调度开销大 | FFRT运行时在用户态以协程方式调度执行,相比内核线程调度机制更为轻量,减小调度的开销,并可通过硬化调度卸载进一步减小调度开销 | 40| 依赖表达 | 线程创建时即处于可执行状态,执行时与其他线程同步操作,增加线程切换 | FFRT运行时根据任务创建时显式表达的输入依赖和输出依赖关系判断任务可执行状态,当输入依赖不满足时,任务不被调度执行 | 41 42 43 44## Function Flow 任务编程模型 45 46Function Flow编程模型允许开发者通过任务及其依赖关系描述的方式进行应用开发,其主要特性包括`Task-Based` 和 `Data-Driven` 。 47 48### Task-Based 特性 49 50`Task-Based` 指在Function Flow编程模型中开发者以任务方式来组织应用程序表达,运行时以任务粒度执行调度。 51 52任务定义为一种面向开发者的编程线索和面向运行时的执行对象,通常包含一组指令序列及其操作的数据上下文环境。 53 54Function Flow编程模型中的任务包含以下主要特征: 55 56- 任务之间可指定依赖关系,依赖关系通过`Data-Driven`方式表达。 57- 任务可支持嵌套,即任务在执行过程中可生成新的任务下发给运行时,形成父子任务关系。 58- 多任务支持互同步操作,例如等待,锁,条件变量等。 59 60> 注意 61> 62> 任务颗粒度影响应用执行性能,颗粒度过小增加调度开销,颗粒度过大降低并行度。Function Flow编程模型中任务的目标颗粒度最小为100us量级,开发者应注意合理控制任务颗粒度。 63 64### Data-Driven 特性 65 66`Data-Driven`指任务之间的依赖关系通过数据依赖表达。 67 68任务执行过程中对其关联的数据对象进行读写操作。在Function Flow编程模型中,数据对象表达抽象为数据签名,每个数据签名唯一对应一个数据对象。 69 70数据依赖抽象为任务所操作的数据对象的数据签名列表,包括输入数据依赖`in_deps`和输出数据依赖`out_deps`。数据对象的签名出现在一个任务的`in_deps`中时,该任务称为数据对象的消费者任务,消费者任务执行不改变其输入数据对象的内容;数据对象的签名出现在任务的`out_deps`中时,该任务称为数据对象的生产者任务,生产者任务执行改变其输出数据对象的内容,从而生成该数据对象的一个新的版本。 71 72一个数据对象可能存在多个版本,每个版本对应一个生产者任务和零个,一个或多个消费者任务,根据生产者任务和消费者任务的下发顺序定义数据对象的多个版本的顺序以及每个版本所对应的生产者和消费者任务。 73 74数据依赖解除的任务进入就绪状态允许被调度执行,依赖解除状态指任务所有输入数据对象版本的生产者任务执行完成,且所有输出数据对象版本的所有消费者任务执行完成的状态。 75 76通过上述`Data-Driven`的数据依赖表达,FFRT在运行时可动态构建任务之间的基于生产者/消费者的数据依赖关系并遵循任务数据依赖状态执行调度,包括: 77 78- Producer-Consumer 依赖 79 80 一个数据对象版本的生产者任务和该数据对象版本的消费者任务之间形成的依赖关系,也称为Read-after-Write依赖。 81 82- Consumer-Producer 依赖 83 84 一个数据对象版本的消费者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Read依赖。 85 86- Producer-Producer 依赖 87 88 一个数据对象版本的生产者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Write依赖。 89 90 91例如,如果有这么一些任务,与数据A的关系表述为: 92```{.cpp} 93task1(OUT A); 94task2(IN A); 95task3(IN A); 96task4(OUT A); 97task5(OUT A); 98``` 99 100<img src="images/image-20220926150341102.png" style="zoom:60%" /> 101 102> 为表述方便,本文中的数据流图均以圆圈表示 Task,方块表示数据。 103 104可以得出以下结论: 105- task1 与task2/task3 构成Producer-Consumer 依赖,即:task2/task3 需要等到task1 写完A之后才能读A 106- task2/task3 与task4 构成Consumer-Producer 依赖,即:task4 需要等到task2/task3 读完A之后才能写A 107- task4 与task5 构成Producer-Producer 依赖,即:task5 需要等到task4 写完A之后才能写A 108 109 110 111# C++ API 112 113> C++ API采用接近C++11的命名风格,以`ffrt`命名空间替代`std`命名空间 114> 需编译使用-std=c++17 115 116## 任务管理 117### submit 118<hr/> 119* 向调度器提交一个task 120* 该接口是异步的,即该接口不等到task完成即可返回,因此,通常与[wait](#wait) 配合使用 121 122#### 声明 123 124```{.cpp} 125namespace ffrt { 126void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {}); 127void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {}); 128} 129``` 130 131#### 参数 132 133`func` 134 135* 可被std::function 接收的一切CPU 可执行体,可以为C++ 定义的Lambda 函数闭包,函数指针,甚至是函数对象 136 137`in_deps` 138 139* 该参数是可选的 140* 该参数用于描述该任务的输入依赖,FFRT 通过数据的虚拟地址作为数据的Signature 来建立依赖 141 142`out_deps` 143 144* 该参数是可选的 145* 该参数用于描述该任务的输出依赖 146* `注意`:该依赖值本质上是一个数值,ffrt没办法区分该值是合理的还是不合理的,会假定输入的值是合理的进行处理;但不建议采用NULL,1, 2 等值来建立依赖关系,建议采用实际的内存地址,因为前者使用不当会建立起不必要的依赖,影响并发 147 148`attr` 149 150* 该参数是可选的 151* 该参数用于描述Task 的属性,比如qos 等,详见 [task_attr](#task_attr)章节 152 153#### 返回值 154 155* 不涉及 156 157#### 描述 158* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用 159 160* 该接口支持嵌套调用,即任务中可以继续提交子任务 161 162* 该接口在实现上使用多个重载版本以优化性能(基于移动语义,初始化列表等),用户只需按上述原型使用,编译时会自动选择最佳版本,支持的重载版本有: 163 164 ```{.cpp} 165 void submit(std::function<void()>&& func); 166 void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps); 167 void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps); 168 void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps, const task_attr& attr); 169 170 void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps); 171 void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps); 172 void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps, const task_attr& attr); 173 174 void submit(const std::function<void()>& func); 175 void submit(const std::function<void()>& func, std::initializer_list<const void*> in_deps); 176 void submit(const std::function<void()>& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps); 177 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); 178 179 void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps); 180 void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps); 181 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); 182 ``` 183 184 185#### 样例 186 187**submit and wait** 188 189```{.cpp} 190#include <iostream> 191#include "ffrt.h" 192 193int main(int narg, char** argv) 194{ 195 int i = 0; 196 for (i = 0; i < 3; i++) { 197 ffrt::submit([i] { std::cout << "num: " << i << std::endl; }); 198 } 199 ffrt::wait(); 200 return 0; 201} 202``` 203 204`解析`: 205 2061) 该示例中连续下发了3个Task,Task 使用C++ 11 Lambda 来描述(实际中Task 还可以使用函数指针,函数对象来描述),这些 Task 都会读取i,但是不会写任何变量; 207 2082) ffrt::submit 为异步下发,因此,Task2 并不会等到 Task1 执行完成之后再下发; 209 2103) ffrt::wait 用于实现待所有Task 都执行完成之后 main 函数再退出; 211 2124) 由于3个Task 在数据依赖关系上没有生产者-消费者或生产者-生产者依赖关系,因此3个 Task 是可以并行的,1种可能的输出是: 213 214```{.cpp} 215num: 0 216num: 2 217num: 1 218``` 219 220`注意`: 221 222如果将Lambda 表达式中的值捕获设置成引用捕获(即`[&i] { std::cout << "num: " << i << std::endl; }`),可能得到的输出为: 223 224```{.cpp} 225num: 2 226num: 2 227num: 2 228``` 229 230这是因为FFRT 是异步编程模型,在第一个task 真正开始执行的时候,i 的值可能已经被修改为1或者2 231 232 233 234**data verison** 235 236<img src="images/image-20220926150341102.png" style="zoom:60%" /> 237 238```{.cpp} 239#include <iostream> 240#include "ffrt.h" 241 242int main(int narg, char** argv) 243{ 244 int x = 1; 245 ffrt::submit([&] {x = 100; std::cout << "x:" << x << std::endl;}, {}, {&x}); 246 ffrt::submit([&] {std::cout << "x:" << x << std::endl;}, {&x}, {}); 247 ffrt::submit([&] {std::cout << "x:" << x << std::endl;}, {&x}, {}); 248 ffrt::submit([&] {x++; std::cout << "x:" << x << std::endl;}, {}, {&x}); 249 ffrt::submit([&] {x++; std::cout << "x:" << x << std::endl;}, {}, {&x}); 250 251 ffrt::wait(); 252 return 0; 253} 254``` 255 256 `解析`: 257 2581) 按上一章节[Data-Driven 特性](#Data-Driven 特性)的描述,输出一定为: 259 260```{.cpp} 261x:100 262x:100 263x:100 264x:101 265x:102 266``` 267 268**nested task** 269 270```{.cpp} 271#include <iostream> 272#include "ffrt.h" 273 274int main(int narg, char** argv) 275{ 276 ffrt::submit([&] { 277 std::cout << "task 1" << std::endl; 278 ffrt::submit([&] {std::cout << "nested task 1.1" << std::endl;}, {}, {}); 279 ffrt::submit([&] {std::cout << "nested task 1.2" << std::endl;}, {}, {}); 280 ffrt::wait(); 281 }, {}, {}); 282 283 ffrt::submit([&] { 284 std::cout << "task 2" << std::endl; 285 ffrt::submit([&] {std::cout << "nested task 2.1" << std::endl;}, {}, {}); 286 ffrt::submit([&] {std::cout << "nested task 2.2" << std::endl;}, {}, {}); 287 ffrt::wait(); 288 }, {}, {}); 289 ffrt::wait(); 290 return 0; 291} 292``` 293 294 `解析`: 295 2961) FFRT允许在 Task 内部继续提交多个SubTask,这样 Task 之间可以建立起一颗调用树; 297 2982) Task1 和Task2 可以并行,Task 1.1/1.2/2.1/2.2 之间也可以并行,因此1种可行的输出为: 299 300``` 301task 1 302nested task 1.1 303task 2 304nested task 1.2 305nested task 2.2 306nested task 2.1 307``` 308 309### wait 310<hr/> 311* 同步等待,与[submit](#submit) 配合使用 312* 等待指定的数据被生产完成,或等待当前任务的所有子任务完成,在不满足条件之前,当前的执行上下文被suspend,在满足条件后恢复执行 313 314#### 声明 315 316```{.cpp} 317namespace ffrt { 318void wait(const std::vector<const void*>& deps); 319void wait(); 320} 321``` 322 323#### 参数 324 325`deps` 326 327* 需要等待被生产完成的数据的虚拟地址,这些地址可能作为某些任务在submit 时的out_deps 328 329#### 返回值 330 331* 不涉及 332 333#### 描述 334* wait(deps) 用于等待deps指代的数据被生产完成才能执行后面的代码 335* wait() 用于等待当前上下文提交的所有子任务(`注意:不包括孙子任务`)都完成才能执行后面的代码 336* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用 337* 在FFRT task 外部调用的wait 是OS 能够感知的等待,相对于FFRT task 内部调用的wait 是更加昂贵的,因此我们希望尽可能让更多的wait 发生在FFRT task 内部 ,而不是FFRT task 外部 338 339#### 样例 340 341**recursive fibonacci** 342 343串行版的fibonacci 可以实现为: 344 345```{.cpp} 346#include <iostream> 347 348void fib(int x, int& y) { 349 if (x <= 1) { 350 y = x; 351 } else { 352 int y1, y2; 353 fib(x - 1, y1); 354 fib(x - 2, y2); 355 y = y1 + y2; 356 } 357} 358 359int main(int narg, char** argv) 360{ 361 int r; 362 fib(10, r); 363 std::cout << "fibonacci 10: " << r << std::endl; 364 return 0; 365} 366``` 367 368若要使用 FFRT 实现并行(注,对于单纯的fibonacci,单个 Task 的计算量极小,不具有并行加速的意义,但这种调用pattern 对并行编程模型的灵活性考验是非常高的),其中1种可行的实现为: 369 370```{.cpp} 371#include <iostream> 372 373#include "ffrt.h" 374 375void fib_ffrt(int x, int& y) 376{ 377 if (x <= 1) { 378 y = x; 379 } else { 380 int y1, y2; 381 ffrt::submit([&] {fib_ffrt(x - 1, y1);}, {&x}, {&y1} ); 382 ffrt::submit([&] {fib_ffrt(x - 2, y2);}, {&x}, {&y2} ); 383 ffrt::wait({&y1, &y2}); 384 y = y1 + y2; 385 } 386} 387 388int main(int narg, char** argv) 389{ 390 int r; 391 ffrt::submit([&] { fib_ffrt(10, r); }, {}, {&r}); 392 ffrt::wait({&r}); 393 std::cout << "fibonacci 10: " << r << std::endl; 394 return 0; 395} 396``` 397 398`解析`: 399 4001) 将fibonacci (x-1)和fibonacci (x-2) 作为2个Task 提交给FFRT,在两个Task 完成之后将结果累加; 401 4022) 虽然单个Task 只能拆分成2个SubTask 但是子Task 可以继续拆分,因此,整个计算图的并行度是非常高的,Task 之间在FFRT 内部形成了一颗调用树; 403 404<img src="images/image-20220926152331554.png" style="zoom:100%" /> 405 406 407### task_attr 408<hr/> 409* 定义task 的属性的辅助类,与[submit](#submit) 配合使用 410 411#### 声明 412 413```{.cpp} 414namespace ffrt { 415enum qos { 416 qos_inherit = -1, 417 qos_background, 418 qos_utility, 419 qos_default, 420 qos_user_initiated, 421}; 422 423class task_attr { 424public: 425 task_attr& qos(enum qos qos); // set qos 426 enum qos qos() const; // get qos 427 task_attr& name(const char* name); // set name 428 const char* name() const; // get name 429}; 430} 431``` 432 433#### 参数 434 435`qos` 436 437* qos 设定的枚举类型 438* inherent 是一个qos 设定策略,代表即将submit 的task 的qos 继承当前task 的qos 439 440#### 返回值 441 442* 不涉及 443 444#### 描述 445* 约定 446 * 在submit 时,如果不通过task_attr 设定qos,那么默认该提交的task的qos 为`qos_default` 447 * 在submit 时,如果通过task_attr 设定qos 为`qos_inherent`,表示将该提交的task 的qos 与当前task 的qos 相同,在FFRT task 外部提交的属性为`qos_inherent` 的task,其qos 为`qos_default` 448 * 其他情况下,该提交的task 的qos 被设定为指定的值 449* qos 级别从上到下依次递增,qos_user_interactive拥有最高优先级 450 451#### 样例 452 453```{.cpp} 454#include <iostream> 455#include "ffrt.h" 456 457int main(int narg, char** argv) 458{ 459 ffrt::submit([] { std::cout << "hello ffrt" << std::endl; }, {}, {}, 460 ffrt::task_attr().qos(ffrt::qos_background)); 461 ffrt::wait(); 462 return 0; 463} 464``` 465 466* 提交一个qos 级别为background 的任务 467 468 469 470### submit_h 471 472<hr/> 473 474* 向调度器提交一个task,与[submit](#submit) 的差别在于返回task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 475 476#### 声明 477 478```{.cpp} 479namespace ffrt { 480class task_handle { 481public: 482 task_handle(); 483 task_handle(ffrt_task_handle_t p); 484 485 task_handle(task_handle const&) = delete; 486 void operator=(task_handle const&) = delete; 487 488 task_handle(task_handle&& h); 489 task_handle& operator=(task_handle&& h); 490 491 operator void* () const; 492}; 493 494task_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 = {}); 495task_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 = {}); 496} 497``` 498 499#### 参数 500 501`func` 502 503* 同submit,详见[submit](#submit) 定义 504 505`in_deps` 506 507* 同submit,详见[submit](#submit) 定义 508 509`out_deps` 510 511* 同submit,详见[submit](#submit) 定义 512 513`attr` 514 515* 同submit,详见[submit](#submit) 定义 516 517#### 返回值 518 519* task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 520 521#### 描述 522 523* 该接口与submit 使用基本相同,从性能的角度,在不需要返回task handle 的场景,可以调用submit 接口相对于submit_h 有更好的性能 524* task_handle 可以和其他的数据depends 同时作为某个task 的in_deps,表示该task 的执行依赖task_handle 对应的task 执行完成 525* task_handle 可以和其他的数据depends 同时作为wait 的deps,表示当前任务将被suspend,直到task_handle 对应的task 执行完成后将被恢复 526* task_handle 不建议作为某个task 的out_deps,其行为是未定义的 527 528 529#### 样例 530 531```{.cpp} 532#include <iostream> 533#include "ffrt.h" 534 535int main(int narg, char** argv) 536{ 537 // handle work with submit 538 ffrt::task_handle h = ffrt::submit_h([] { std::cout << "hello "; }); // not need some data in this task 539 int x = 1; 540 ffrt::submit([&] { x++; }, {}, {&x}); 541 ffrt::submit([&] { std::cout << "world, x = " << x << std::endl; }, {&x, h}); // this task depend x and h 542 543 // handle work with wait 544 ffrt::task_handle h2 = ffrt::submit_h([&] { std::cout << "handle wait" << std::endl; x++; }); 545 ffrt::wait({h2}); 546 std::cout << "x = " << x << std::endl; 547 ffrt::wait(); 548 return 0; 549} 550``` 551 552* 预期的输出为 553 554``` 555hello world, x = 2 556handle wait 557x = 3 558``` 559 560### get_id 561 562<hr/> 563 564* 返回当前task的id标识,更多使用用于维测(原因是task name可能重名) 565 566#### 声明 567 568```{.cpp} 569namespace ffrt { 570namespace this_task { 571uint64_t get_id(); 572} 573} 574``` 575 576#### 参数 577 578* 不涉及 579 580#### 返回值 581 582* 当前task的id 583 584#### 描述 585 586* 该接口在task内部调用将返回当前task的id标识,在task外部调用将返回0 587* 可以基于该接口在task外部调用返回0的特性来区分函数是运行在FFRT 工作线程上还是非FFRT工作线程上 588* task id为从1开始编码,每提交一个task便增加1,被设计成64bit,即便是每秒百万次提交,也需要292471.2年才会发生翻转 589 590#### 样例 591 592```{.cpp} 593#include <iostream> 594#include "ffrt.h" 595 596int main(int narg, char** argv) 597{ 598 ffrt::submit([] { std::cout << "task id: " << ffrt::this_task::get_id() << std::endl; }); 599 ffrt::submit([] { std::cout <<"task id: " << ffrt::this_task::get_id() << std::endl; }); 600 ffrt::wait(); 601 std::cout << "task id: " << ffrt::this_task::get_id() << std::endl; 602 return 0; 603} 604``` 605 606* 可能的输出为: 607 608``` 609task id: 1 610task id: 2 611task id: 0 612``` 613 614### update_qos 615 616<hr/> 617 618* 更新当前正在执行的task的优先级 619 620#### 声明 621 622```{.cpp} 623namespace ffrt { 624namespace this_task { 625int update_qos(enum qos qos); 626} 627} 628``` 629 630#### 参数 631 632`qos` 633* 新的qos等级 634 635#### 返回值 636 637* 0表示成功,非0表示失败 638 639#### 描述 640 641* 该接口对当前task的qos调整会立即生效 642* 如果新设定的qos与当前的qos不一致,则会block当前task的执行,再按照新的qos恢复执行 643* 如果新设定的qos与当前的qos一致,则接口会立即返回,不做任何处理 644* **如果在非task内部调用该接口,则返回非0值,用户可以选择忽略或其他处理** 645 646#### 样例 647 648```{.cpp} 649#include <iostream> 650#include <thread> 651#include "ffrt.h" 652 653int main(int narg, char** argv) 654{ 655 ffrt::submit([] { 656 std::cout << "thread id: " << std::this_thread::get_id() << std::endl; 657 std::cout << "return " << ffrt::this_task::update_qos(ffrt::qos_user_initiated) << std::endl; 658 std::cout << "thread id: " << std::this_thread::get_id() << std::endl; 659 }); 660 ffrt::wait(); 661 std::cout << "return " << ffrt::this_task::update_qos(ffrt::qos_user_initiated) << std::endl; 662 return 0; 663} 664``` 665 666* 可能的输出为: 667 668``` 669thread id: 1024 670return 0 671thread id: 2222 672return 1 673``` 674 675 676 677## 串行队列 678<hr /> 679* FFRT提供queue来实现Andorid中类似WorkQueue能力,且在使用得当的情况下将有更好的性能 680 681* 串行队列支持提交延时任务,提交后会等待大于等于设定的延时时间再执行。当系统资源充分时,满足ms级精度要求;当系统资源紧张时,串行任务只保证计算顺序 682 683* 串行队列的watchdog机制,串行任务执行超过30s会上报超时,创建串行任务时注意不要提交会执行超过30s的任务,例如死循环任务 684 685### submit 686<hr/> 687 688#### 声明 689```{.c} 690namespace ffrt { 691void submit(std::function<void()>& func, const task_attr& attr); 692void submit(std::function<void()>&& func); 693void submit(std::function<void()>&& func, const task_attr& attr); 694} 695``` 696 697#### 参数 698`func` 699* 可被std::function接收的一切CPU可执行体,可以为C++定义的Lambda函数闭包,函数指针,甚至是函数对象 700 701`attr` 702* 该参数是可选的 703* 该参数用于描述Task的属性,比如qos等,详见task_attr章节 704 705#### 返回值 706* 不涉及 707 708#### 描述 709* 提交一个任务到队列中调度执行 710 711#### 样例 712``` 713#include <stdio.h> 714#include "ffrt.h" 715 716using namespace ffrt; 717using namespace std; 718 719int main(int narg, char** argv) 720{ 721 queue* testQueue = new queue("test_queue"); 722 723 int x = 0; 724 testQueue->submit([&x] { x += 10; }); 725 usleep(100); 726 727 delete testQueue; 728} 729``` 730 731### submit_h 732<hr/> 733 734#### 声明 735```{.c} 736namespace ffrt { 737task_handle submit_h(std::function<void()>& func); 738task_handle submit_h(std::function<void()>& func, const task_attr& attr); 739task_handle submit_h(std::function<void()>&& func); 740task_handle submit_h(std::function<void()>&& func, const task_attr& attr); 741} 742``` 743 744#### 参数 745`func` 746* 可被std::function接收的一切CPU可执行体,可以为C++定义的Lambda函数闭包,函数指针,甚至是函数对象 747 748`attr` 749* 该参数是可选的 750* 该参数用于描述Task的属性,比如qos等,详见task_attr章节 751 752#### 返回值 753`task_handle` 754* task的句柄,该句柄可以用于建立task之间的依赖 755 756#### 描述 757* 提交一个任务到队列中调度执行,并返回一个句柄 758 759#### 样例 760``` 761#include <stdio.h> 762#include "ffrt.h" 763 764using namespace ffrt; 765using namespace std; 766 767int main(int narg, char** argv) 768{ 769 queue* testQueue = new queue("test_queue"); 770 771 int x = 0; 772 testQueue->submit([&x] { x += 10; }); 773 task_handle task = testQueue->submit_h([&x] { x += 10; }); 774 testQueue->wait(task); 775 776 delete testQueue; 777} 778``` 779 780### cancel 781<hr/> 782 783#### 声明 784```{.c} 785namespace ffrt { 786int cancel(task_handle& handle); 787} 788``` 789 790#### 参数 791`handle` 792* 任务的句柄 793 794#### 返回值 795* 若成功返回0,否则返回-1 796 797#### 描述 798* 根据句柄取消对应的任务。该任务若为延时任务且未到期望执行的时间则可以正常被取消;若欲取消无延时任务,则有概率取消失败。 799 800#### 样例 801``` 802#include <stdio.h> 803#include "ffrt.h" 804 805using namespace ffrt; 806using namespace std; 807 808int main(int narg, char** argv) 809{ 810 queue* testQueue = new queue("test_queue", queue_attr().qos(qos_utility)); 811 812 int x = 0; 813 task_handle t1 = testQueue->submit_h([&x] { x += 10; }); 814 task_handle t2 = testQueue->submit_h([&x] { x += 10; }, task_attr().delay(1000)); 815 testQueue->wait(t1); 816 int ret = testQueue->cancel(t2); 817 818 delete testQueue; 819} 820``` 821 822### wait 823<hr/> 824 825#### 声明 826```{.c} 827namespace ffrt { 828void wait(task_handle& handle); 829} 830``` 831 832#### 参数 833`handle` 834* 任务的句柄 835 836#### 返回值 837* 不涉及 838 839#### 描述 840* 等待句柄对应的任务执行完成 841 842#### 样例 843* 见[submit_h](#submit_h)章节样例 844 845### qos 846<hr/> 847 848#### 声明 849```{.c} 850namespace ffrt { 851queue_attr& qos(qos qos_){ 852 ffrt_queue_attr_set_qos(this, qos_); 853 return *this; 854} 855} 856``` 857 858#### 参数 859`qos_` 860* 见[ffrt_queue_attr_set_qos](#ffrt_queue_attr_set_qos) 861 862#### 返回值 863* 见[ffrt_queue_attr_set_qos](#ffrt_queue_attr_set_qos) 864 865#### 描述 866* 设置串行队列qos属性 867 868#### 样例 869* 见[ffrt_queue_t](#ffrt_queue_t)章节样例 870 871 872### qos 873<hr/> 874 875#### 声明 876```{.c} 877namespace ffrt { 878int qos() const { 879 return ffrt_queue_attr_get_qos(this); 880} 881} 882``` 883 884#### 参数 885* 无 886 887#### 返回值 888* 同ffrt_queue_attr_get_qos,详见[ffrt_queue_attr_get_qos](#ffrt_queue_attr_get_qos) 889 890#### 描述 891* 获取串行队列qos属性,默认为default等级 892 893#### 样例 894* 见[ffrt_queue_t](#ffrt_queue_t)章节样例 895 896 897### timeout 898<hr/> 899 900#### 声明 901```{.c} 902namespace ffrt { 903queue_attr& timeout(uint64_t timeout_us) { 904 ffrt_queue_attr_set_timeout(this, timeout_us); 905 return *this; 906} 907} 908``` 909 910#### 参数 911`timeout_us` 912* 见[ffrt_queue_attr_set_timeout](#ffrt_queue_attr_set_timeout) 913 914#### 返回值 915* 见[ffrt_queue_attr_set_timeout](#ffrt_queue_attr_set_timeout) 916 917#### 描述 918* 设置串行队列任务执行超时时间 919 920#### 样例 921* 见[ffrt_queue_attr_set_timeout](#ffrt_queue_attr_set_timeout)章节样例 922 923 924### timeout 925<hr/> 926 927#### 声明 928```{.c} 929namespace ffrt { 930uint64_t timeout() const { 931 return ffrt_queue_attr_get_timeout(this); 932} 933} 934``` 935 936#### 参数 937* 无 938 939#### 返回值 940* 见[ffrt_queue_attr_get_timeout](#ffrt_queue_attr_get_timeout) 941 942#### 描述 943* 获取所设的串行队列任务执行超时时间 944 945#### 样例 946* 见[ffrt_queue_attr_set_timeout](#ffrt_queue_attr_set_timeout)章节样例 947 948 949### callback 950<hr/> 951 952#### 声明 953```{.c} 954namespace ffrt { 955queue_attr& callback(std::function<void()>& func) { 956 ffrt_queue_attr_set_callback(this, create_function_wrapper(func, ffrt_function_kind_queue)); 957 return *this; 958} 959} 960``` 961 962#### 参数 963`func` 964* 见[ffrt_queue_attr_set_callback](#ffrt_queue_attr_set_callback) 965 966#### 返回值 967* 见[ffrt_queue_attr_set_callback](#ffrt_queue_attr_set_callback) 968 969#### 描述 970* 设置所设的串行队列超时回调函数 971 972#### 样例 973* 见[ffrt_queue_attr_set_callback](#ffrt_queue_attr_set_callback)章节样例 974 975 976### callback 977<hr/> 978 979#### 声明 980```{.c} 981namespace ffrt { 982ffrt_function_header_t* callback() const { 983 return ffrt_queue_attr_get_callback(this); 984} 985} 986``` 987 988#### 参数 989* 无 990 991#### 返回值 992* 见[ffrt_queue_attr_get_callback](#ffrt_queue_attr_get_callback) 993 994#### 描述 995* 获取串行队列超时回调函数 996 997#### 样例 998* 见[ffrt_queue_attr_set_callback](#ffrt_queue_attr_set_callback)章节样例 999 1000 1001 1002 1003## 同步原语 1004 1005### mutex 1006<hr/> 1007* FFRT提供的类似std::mutex 的性能实现 1008 1009#### 声明 1010 1011```{.cpp} 1012namespace ffrt { 1013class mutex { 1014public: 1015 mutex(mutex const &) = delete; 1016 void operator =(mutex const &) = delete; 1017 1018 void lock(); 1019 void unlock(); 1020 bool try_lock(); 1021}; 1022} 1023``` 1024 1025#### 参数 1026 1027* 不涉及 1028 1029#### 返回值 1030 1031* 不涉及 1032 1033#### 描述 1034* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 1035* 该功能能够避免传统的std::mutex 在抢不到锁时陷入内核的问题,在使用得当的条件下将会有更好的性能 1036 1037#### 样例 1038 1039```{.cpp} 1040#include <iostream> 1041#include "ffrt.h" 1042 1043void ffrt_mutex_task() 1044{ 1045 int sum = 0; 1046 ffrt::mutex mtx; 1047 for (int i = 0; i < 10; i++) { 1048 ffrt::submit([&sum, i, &mtx] { 1049 mtx.lock(); 1050 sum = sum + i; 1051 mtx.unlock(); 1052 }, {}, {}); 1053 } 1054 ffrt::wait(); 1055 std::cout << "sum = " << sum << std::endl; 1056} 1057 1058int main(int narg, char** argv) 1059{ 1060 int r; 1061 ffrt::submit(ffrt_mutex_task); 1062 ffrt::wait(); 1063 return 0; 1064} 1065``` 1066 1067预期输出为 1068 1069``` 1070sum=45 1071``` 1072 1073* 该例子为功能示例,实际中并不鼓励这样使用 1074 1075 1076### condition_variable 1077<hr/> 1078 1079* FFRT提供的类似std::condition_variable 的性能实现 1080 1081#### 声明 1082 1083```{.cpp} 1084namespace ffrt { 1085enum class cv_status { 1086 no_timeout, 1087 timeout 1088}; 1089 1090class condition_variable { 1091public: 1092 using TimePoint = std::chrono::steady_clock::time_point; 1093 template<typename Clock, typename Duration, typename Pred> 1094 bool wait_until(std::unique_lock<mutex>& lk, 1095 const std::chrono::time_point<Clock, Duration>& tp, 1096 Pred&& pred) noexcept; 1097 1098 template<typename Clock, typename Duration> 1099 cv_status wait_until(std::unique_lock<mutex>& lk, 1100 const std::chrono::time_point<Clock, Duration>& tp) noexcept; 1101 1102 template<typename Rep, typename Period> 1103 cv_status wait_for(std::unique_lock<mutex>& lk, 1104 const std::chrono::duration<Rep, Period>& sleep_time) noexcept; 1105 1106 template<typename Rep, typename Period, typename Pred> 1107 bool wait_for(std::unique_lock<mutex>& lk, 1108 const std::chrono::duration<Rep, Period>& sleepTime, 1109 Pred&& pred) noexcept; 1110 1111 void wait(std::unique_lock<mutex>& lk); 1112 1113 template<typename Pred> 1114 void wait(std::unique_lock<mutex>& lk, Pred&& pred); 1115 1116 void notify_one() noexcept; 1117 1118 void notify_all() noexcept; 1119}; 1120} 1121``` 1122 1123#### 参数 1124 1125`lk` 1126* mutex互斥量 1127`tp` 1128* 等待时间 1129`sleep_time` 1130* 等待时间 1131`pred` 1132* 检查是否等待函数 1133#### 返回值 1134 1135* 不涉及 1136 1137#### 描述 1138* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 1139* 该功能能够避免传统的std::condition_variable 在条件不满足时陷入内核的问题,在使用得当的条件下将会有更好的性能 1140 1141#### 样例 1142 1143```{.cpp} 1144#include <iostream> 1145#include "ffrt.h" 1146 1147void ffrt_cv_task() 1148{ 1149 ffrt::condition_variable cond; 1150 int a = 0; 1151 ffrt::mutex lock_; 1152 ffrt::submit([&] { 1153 std::unique_lock lck(lock_); 1154 cond.wait(lck, [&] { return a == 1; }); 1155 std::cout << "a = " << a << std::endl; 1156 }, {}, {}); 1157 ffrt::submit([&] { 1158 std::unique_lock lck(lock_); 1159 a = 1; 1160 cond.notify_one(); 1161 }, {}, {}); 1162 1163 ffrt::wait(); 1164} 1165 1166int main(int narg, char** argv) 1167{ 1168 int r; 1169 ffrt::submit(ffrt_cv_task); 1170 ffrt::wait(); 1171 return 0; 1172} 1173 1174``` 1175 1176预期输出为: 1177 1178``` 1179a=1 1180``` 1181 1182* 该例子为功能示例,实际中并不鼓励这样使用 1183 1184## 杂项 1185 1186### sleep 1187 1188<hr/> 1189* FFRT提供的类似std::this_thread::sleep_for / std::this_thread::sleep_until 的性能实现 1190 1191#### 声明 1192 1193```{.cpp} 1194namespace ffrt { 1195namespace this_task { 1196template<class _Rep, class _Period> 1197void sleep_for(const std::chrono::duration<_Rep, _Period>& sleep_duration); 1198 1199template<class _Clock, class _Duration> 1200void sleep_until(const std::chrono::time_point<_Clock, _Duration>& sleep_time); 1201} 1202} 1203``` 1204 1205#### 参数 1206 1207`sleep_duration` 1208 1209* 睡眠的时长 1210 1211`sleep_time` 1212 1213* 睡眠到达的时间点 1214 1215#### 返回值 1216 1217* 不涉及 1218 1219#### 描述 1220* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 1221* 该功能能够避免传统的std::this_thread::sleep_for 睡眠时陷入内核的问题,在使用得当的条件下将会有更好的性能 1222* 该接口调用后实际睡眠时长不小于配置值 1223 1224#### 样例 1225 1226```{.cpp} 1227#include <chrono> 1228#include <iostream> 1229#include "ffrt.h" 1230 1231using namespace std::chrono_literals; 1232int main(int narg, char** argv) 1233{ 1234 ffrt::submit([] { 1235 std::cout << "Hello waiter\n" << std::flush; 1236 auto start = std::chrono::high_resolution_clock::now(); 1237 ffrt::this_task::sleep_for(2000ms); 1238 auto end = std::chrono::high_resolution_clock::now(); 1239 std::chrono::duration<double, std::milli> elapsed = end-start; 1240 std::cout << "Waited " << elapsed.count() << " ms\n"; 1241 }); 1242 ffrt::wait(); 1243 return 0; 1244} 1245``` 1246 1247* 预期输出为 1248 1249``` 1250Hello waiter 1251Waited 2000.12 ms 1252``` 1253 1254### yield 1255<hr/> 1256* 当前task 主动让出CPU 执行资源,让其他可以被执行的task 先执行,如果没有其他可被执行的task,yield 无效 1257 1258#### 声明 1259 1260```{.cpp} 1261namespace ffrt { 1262namespace this_task { 1263void yield(); 1264} 1265} 1266``` 1267 1268#### 参数 1269 1270* 不涉及 1271 1272#### 返回值 1273 1274* 不涉及 1275 1276#### 描述 1277* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 1278* 此函数的确切行为取决于实现,特别是使用中的FFRT 调度程序的机制和系统状态 1279 1280#### 样例 1281 1282```{.cpp} 1283#include <chrono> 1284#include "ffrt.h" 1285 1286using namespace std::chrono_literals; 1287// "busy sleep" while suggesting that other tasks run 1288// for a small amount of time 1289void little_sleep(std::chrono::microseconds us) 1290{ 1291 auto start = std::chrono::high_resolution_clock::now(); 1292 auto end = start + us; 1293 do { 1294 ffrt::this_task::yield(); 1295 } while (std::chrono::high_resolution_clock::now() < end); 1296} 1297 1298int main(int narg, char** argv) 1299{ 1300 ffrt::submit([] { little_sleep(200us); }); 1301 ffrt::wait(); 1302 return 0; 1303} 1304``` 1305 1306* 这是一个`busy sleep`,同时允许其他可以被执行的task 插入执行 1307 1308 1309# C API 1310 1311> C API采用接近C11/pthread (https://zh.cppreference.com/w/c) 的命名风格,并冠以`ffrt_`前缀,以`_base`为后缀的API是内部API,通常不被用户直接调用 1312> 1313> **出于易用性方面的考虑,除非必要,强烈建议你使用C++ API(亦满足二进制兼容要求),调用C API将会使你的代码非常臃肿** 1314 1315## 任务管理 1316 1317### ffrt_submit_base 1318 1319* 该接口为ffrt动态库的导出接口,基于此可以封装出不同的C++ API ffrt::submit和C API ffrt_submit,满足二进制兼容 1320 1321#### 声明 1322 1323```{.cpp} 1324const int ffrt_auto_managed_function_storage_size = 64 + sizeof(ffrt_function_header_t); 1325typedef enum { 1326 ffrt_function_kind_general, 1327 ffrt_function_kind_queue 1328} ffrt_function_kind_t; 1329 1330void* ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_t kind); 1331 1332typedef void(*ffrt_function_t)(void*); 1333typedef struct { 1334 ffrt_function_t exec; 1335 ffrt_function_t destroy; 1336 uint64_t reserve[2]; 1337} ffrt_function_header_t; 1338 1339void 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); 1340``` 1341 1342#### 参数 1343 1344`kind` 1345 1346* function子类型,用于优化内部数据结构,默认使用ffrt_function_kind_general类型 1347 1348`func` 1349 1350* CPU Function的指针,该指针执行的数据结构,按照`ffrt_function_header_t`定义的描述了该CPU Task如何执行和销毁的函数指针,FFRT通过这两个函数指针完成Task的执行和销毁 1351 1352`in_deps` 1353 1354* 同ffrt_submit 1355 1356 1357`out_deps` 1358 1359* 同ffrt_submit 1360 1361`attr` 1362 1363* 同ffrt_submit 1364 1365#### 返回值 1366 1367* 不涉及 1368 1369#### 描述 1370 1371* ffrt_submit_base不建议用户直接调用,推荐使用基于此封装的C++接口(亦满足二进制兼容) 1372* **ffrt_submit_base作为底层能力,只有在用户需要自定义task类型时使用,使用时需要满足以下限制:** 1373 * ffrt_submit_base入参中的func指针只能通过ffrt_alloc_auto_managed_function_storage_base申请,且二者的调用需一一对应 1374 * ffrt_alloc_auto_managed_function_storage_base申请的内存为ffrt_auto_managed_function_storage_size字节,其生命周期归ffrt管理,在该task结束时,由FFRT自动释放,用户无需释放 1375* ffrt_function_header_t 中定义了两个函数指针: 1376 * exec:用于描述该Task如何被执行,当FFRT需要执行该Task时由FFRT调用 1377 * destroy:用于描述该Task如何被执行,当FFRT需要执行该Task时由FFRT调用 1378 1379#### 样例 1380 1381* 通过该接口提供C++11 Lambda表达式的支持(该代码已经在ffrr.h中提供,默认支持) 1382 1383```{.cpp} 1384template<class T> 1385struct function { 1386 template<class CT> 1387 function(ffrt_function_header_t h, CT&& c) : header(h), closure(std::forward<CT>(c)) {} 1388 ffrt_function_header_t header; 1389 T closure; 1390}; 1391 1392template<class T> 1393void exec_function_wrapper(void* t) 1394{ 1395 auto f = (function<std::decay_t<T>>*)t; 1396 f->closure(); 1397} 1398 1399template<class T> 1400void destroy_function_wrapper(void* t) 1401{ 1402 auto f = (function<std::decay_t<T>>*)t; 1403 f->closure = nullptr; 1404} 1405 1406template<class T> 1407inline ffrt_function_header_t* create_function_wrapper(T&& func) 1408{ 1409 using function_type = function<std::decay_t<T>>; 1410 static_assert(sizeof(function_type) <= ffrt_auto_managed_function_storage_size, 1411 "size of function must be less than ffrt_auto_managed_function_storage_size"); 1412 1413 auto p = ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1414 auto f = new (p) function_type( 1415 {exec_function_wrapper<T>, destroy_function_wrapper<T>}, 1416 std::forward<T>(func)); 1417 return (ffrt_function_header_t*)f; 1418} 1419 1420static inline void submit(std::function<void()>&& func) 1421{ 1422 return ffrt_submit_base(create_function_wrapper(std::move(func)), NULL, NULL, NULL); 1423} 1424``` 1425 1426### ffrt_wait 1427 1428<hr/> 1429* 同步等待,与ffrt_submit 配合使用 1430* 等待指定的数据被生产完成,或等待当前任务的所有子任务完成,在不满足条件之前,当前的执行上下文被suspend,在满足条件后恢复执行 1431 1432#### 声明 1433 1434```{.cpp} 1435void ffrt_wait_deps(ffrt_deps_t* deps); 1436void ffrt_wait(); 1437``` 1438 1439#### 参数 1440 1441`deps` 1442 1443* 需要等待被生产完成的数据的虚拟地址,这些地址可能作为某些任务在submit 时的out_deps,该依赖的生成见ffrt_deps_t章节,空指针表示无依赖 1444 1445#### 返回值 1446 1447* 不涉及 1448 1449#### 描述 1450* ffrt_wait_deps(deps) 用于等待deps指代的数据被生产完成才能执行后面的代码 1451* ffrt_wait() 用于等待当前上下文提交的所有子任务(`注意:不包括孙任务和下级子任务`)都完成才能执行后面的代码 1452* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用 1453* 在FFRT task 外部调用的wait 是OS 能够感知的等待,相对于FFRT task 内部调用的wait 是更加昂贵的,因此我们希望尽可能让更多的wait 发生在FFRT task 内部 ,而不是FFRT task 外部 1454 1455#### 样例 1456 1457**recursive fibonacci** 1458 1459串行版的fibonacci 可以实现为: 1460 1461```{.c} 1462#include <stdio.h> 1463 1464void fib(int x, int* y) { 1465 if (x <= 1) { 1466 *y = x; 1467 } else { 1468 int y1, y2; 1469 fib(x - 1, &y1); 1470 fib(x - 2, &y2); 1471 *y = y1 + y2; 1472 } 1473} 1474int main(int narg, char** argv) 1475{ 1476 int r; 1477 fib(10, &r); 1478 printf("fibonacci 10: %d\n", r); 1479 return 0; 1480} 1481``` 1482 1483若要使用 FFRT 实现并行(注,对于单纯的fibonacci,单个 Task 的计算量极小,不具有并行加速的意义,但这种调用pattern 对并行编程模型的灵活性考验是非常高的),其中1种可行的实现为: 1484 1485```{.c} 1486#include <stdio.h> 1487#include "ffrt.h" 1488 1489typedef struct { 1490 int x; 1491 int* y; 1492} fib_ffrt_s; 1493 1494typedef struct { 1495 ffrt_function_header_t header; 1496 ffrt_function_t func; 1497 ffrt_function_t after_func; 1498 void* arg; 1499} c_function; 1500 1501static void ffrt_exec_function_wrapper(void* t) 1502{ 1503 c_function* f = (c_function*)t; 1504 if (f->func) { 1505 f->func(f->arg); 1506 } 1507} 1508 1509static void ffrt_destroy_function_wrapper(void* t) 1510{ 1511 c_function* f = (c_function*)t; 1512 if (f->after_func) { 1513 f->after_func(f->arg); 1514 } 1515} 1516 1517#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 1518static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 1519 const ffrt_function_t after_func, void* arg) 1520{ 1521 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 1522 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 1523 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1524 f->header.exec = ffrt_exec_function_wrapper; 1525 f->header.destroy = ffrt_destroy_function_wrapper; 1526 f->func = func; 1527 f->after_func = after_func; 1528 f->arg = arg; 1529 return (ffrt_function_header_t*)f; 1530} 1531 1532static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 1533 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1534{ 1535 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1536} 1537 1538#define ffrt_deps_define(name, dep1, ...) const void* __v_##name[] = {dep1, ##__VA_ARGS__}; \ 1539 ffrt_deps_t name = {sizeof(__v_##name) / sizeof(void*), __v_##name} 1540 1541void fib_ffrt(void* arg) 1542{ 1543 fib_ffrt_s* p = (fib_ffrt_s*)arg; 1544 int x = p->x; 1545 int* y = p->y; 1546 1547 if (x <= 1) { 1548 *y = x; 1549 } else { 1550 int y1, y2; 1551 fib_ffrt_s s1 = {x - 1, &y1}; 1552 fib_ffrt_s s2 = {x - 2, &y2}; 1553 ffrt_deps_define(dx, &x); 1554 ffrt_deps_define(dy1, &y1); 1555 ffrt_deps_define(dy2, &y2); 1556 ffrt_deps_define(dy12, &y1, &y2); 1557 ffrt_submit_c(fib_ffrt, NULL, &s1, &dx, &dy1, NULL); 1558 ffrt_submit_c(fib_ffrt, NULL, &s2, &dx, &dy2, NULL); 1559 ffrt_wait_deps(&dy12); 1560 *y = y1 + y2; 1561 } 1562} 1563 1564int main(int narg, char** argv) 1565{ 1566 int r; 1567 fib_ffrt_s s = {10, &r}; 1568 ffrt_deps_define(dr, &r); 1569 ffrt_submit_c(fib_ffrt, NULL, &s, NULL, &dr, NULL); 1570 ffrt_wait_deps(&dr); 1571 printf("fibonacci 10: %d\n", r); 1572 return 0; 1573} 1574``` 1575 1576`解析`: 1577 15781) 将fibonacci (x-1)和fibonacci (x-2) 作为2个Task 提交给FFRT,在两个Task 完成之后将结果累加; 1579 15802) 虽然单个Task 只能拆分成2个SubTask 但是子Task 可以继续拆分,因此,整个计算图的并行度是非常高的,Task 之间在FFRT 内部形成了一颗调用树; 1581 1582<img src="images/image-20220926152331554.png" style="zoom:100%" /> 1583 1584> 以上实现,逻辑上虽与C++ API中的实现类似,但是用户显式管理数据生命周期和函数入参打包两个因素将使代码异常复杂 1585 1586 1587 1588### ffrt_deps_t 1589 1590* C API中对依赖数组的抽象,逻辑上等同于C++ API中的`std::vector<void*>` 1591 1592#### 声明 1593 1594```{.cpp} 1595typedef struct { 1596 uint32_t len; 1597 const void* const * items; 1598} ffrt_deps_t; 1599``` 1600 1601#### 参数 1602 1603`len` 1604 1605* 所依赖的Signature的个数,取值大于等于0 1606 1607`item` 1608 1609* len个Signature的起始地址指针 1610 1611#### 返回值 1612 1613* 不涉及 1614 1615#### 描述 1616 1617* item为len个Signature的起始指针,该指针可以指向堆空间,也可以指向栈空间,但是要求分配的空间大于等于len * sizeof(void*) 1618 1619#### 样例 1620 1621* item指向栈空间的ffrt_deps_t 1622 1623```{.c} 1624#include "ffrt.h" 1625 1626int main(int narg, char** argv) 1627{ 1628 int x1 = 1; 1629 int x2 = 2; 1630 1631 void *t[] = {&x1, &x2}; 1632 ffrt_deps_t deps = {2, (const void* const *)&t}; 1633 // some code use deps 1634 return 0; 1635} 1636``` 1637 1638* item指向栈空间的ffrt_deps_t 1639 1640```{.c} 1641#include <stdlib.h> 1642#include "ffrt.h" 1643 1644int main(int narg, char** argv) 1645{ 1646 int x1 = 1; 1647 int x2 = 2; 1648 1649 void** t = (void**)malloc(sizeof(void*) * 2); 1650 t[0]= &x1; 1651 t[1]= &x2; 1652 ffrt_deps_t deps = {2, t}; 1653 1654 // some code use deps 1655 free(t); 1656 return 0; 1657} 1658``` 1659 1660### ffrt_task_attr_t 1661 1662<hr/> 1663* 定义task 的属性的辅助类,与ffrt_submit 配合使用 1664 1665#### 声明 1666 1667```{.c} 1668typedef enum { 1669 ffrt_qos_inherent = -1, 1670 ffrt_qos_background, 1671 ffrt_qos_utility, 1672 ffrt_qos_default, 1673 ffrt_qos_user_initiated, 1674} ffrt_qos_t; 1675 1676typedef struct { 1677 char storage[ffrt_task_attr_storage_size]; 1678} ffrt_task_attr_t; 1679typedef void* ffrt_task_handle_t; 1680 1681int ffrt_task_attr_init(ffrt_task_attr_t* attr); 1682void ffrt_task_attr_destroy(ffrt_task_attr_t* attr); 1683void ffrt_task_attr_set_qos(ffrt_task_attr_t* attr, ffrt_qos_t qos); 1684ffrt_qos_t ffrt_task_attr_get_qos(const ffrt_task_attr_t* attr); 1685void ffrt_task_attr_set_name(ffrt_task_attr_t* attr, const char* name); 1686const char* ffrt_task_attr_get_name(const ffrt_task_attr_t* attr); 1687``` 1688 1689#### 参数 1690 1691`attr` 1692 1693* 创建的tasks属性的句柄 1694 1695`qos` 1696 1697* qos 设定的枚举类型 1698* inherent 是一个qos 设定策略,代表即将ffrt_submit 的task 的qos 继承当前task 的qos 1699 1700#### 返回值 1701 1702* 不涉及 1703 1704#### 描述 1705* `attr`所传递的内容会在ffrt_submit内部完成取存,ffrt_submit返回后用户即可销毁 1706* 约定 1707 * 在submit 时,如果不通过task_attr 设定qos,那么默认该提交的task的qos 为`ffrt_qos_default` 1708 * 在submit 时,如果通过task_attr 设定qos 为`ffrt_qos_inherent`,表示将该提交的task 的qos 与当前task 的qos 相同,在FFRT task 外部提交的属性为`ffrt_qos_inherent` 的task,其qos 为`ffrt_qos_default` 1709 * 其他情况下,该提交的task 的qos 被设定为指定的值 1710* ffrt_task_attr_t对象的置空和销毁由用户完成,对同一个ffrt_task_attr_t仅能调用一次`ffrt_task_attr_destroy`,重复对同一个ffrt_task_attr_t调用`ffrt_task_attr_destroy`,其行为是未定义的 1711* 在`ffrt_task_attr_destroy`之后再对task_attr进行访问,其行为是未定义的 1712 1713#### 样例 1714 1715```{.c} 1716#include <stdio.h> 1717#include "ffrt.h" 1718 1719void my_print(void* arg) 1720{ 1721 printf("hello ffrt\n"); 1722} 1723 1724typedef struct { 1725 ffrt_function_header_t header; 1726 ffrt_function_t func; 1727 ffrt_function_t after_func; 1728 void* arg; 1729} c_function; 1730 1731static void ffrt_exec_function_wrapper(void* t) 1732{ 1733 c_function* f = (c_function*)t; 1734 if (f->func) { 1735 f->func(f->arg); 1736 } 1737} 1738 1739static void ffrt_destroy_function_wrapper(void* t) 1740{ 1741 c_function* f = (c_function*)t; 1742 if (f->after_func) { 1743 f->after_func(f->arg); 1744 } 1745} 1746 1747#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 1748static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 1749 const ffrt_function_t after_func, void* arg) 1750{ 1751 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 1752 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 1753 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1754 f->header.exec = ffrt_exec_function_wrapper; 1755 f->header.destroy = ffrt_destroy_function_wrapper; 1756 f->func = func; 1757 f->after_func = after_func; 1758 f->arg = arg; 1759 return (ffrt_function_header_t*)f; 1760} 1761 1762static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 1763 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1764{ 1765 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1766} 1767 1768int main(int narg, char** argv) 1769{ 1770 ffrt_task_attr_t attr; 1771 ffrt_task_attr_init(&attr); 1772 ffrt_task_attr_set_qos(&attr, ffrt_qos_background); 1773 ffrt_submit_c(my_print, NULL, NULL, NULL, NULL, &attr); 1774 ffrt_task_attr_destroy(&attr); 1775 ffrt_wait(); 1776 return 0; 1777} 1778``` 1779 1780* 提交一个qos 级别为background 的任务 1781 1782 1783 1784### ffrt_submit_h 1785 1786<hr/> 1787 1788* 向调度器提交一个task,与ffrt_submit 的差别在于返回task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 1789 1790#### 声明 1791 1792```{.cpp} 1793typedef void* ffrt_task_handle_t; 1794 1795ffrt_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); 1796void ffrt_task_handle_destroy(ffrt_task_handle_t handle); 1797``` 1798 1799#### 参数 1800 1801`func` 1802 1803* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1804 1805`in_deps` 1806 1807* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1808 1809`out_deps` 1810 1811* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1812 1813`attr` 1814 1815* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1816 1817#### 返回值 1818 1819* task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 1820 1821#### 描述 1822 1823* C API中的ffrt_task_handle_t的使用与C++ API中的ffrt::task_handle相同 1824* **差异在于:C API中的ffrt_task_handle_t需要用户调用`ffrt_task_handle_destroy`显式销毁,而C++ API无需该操作** 1825* C API中的task_handle_t对象的置空和销毁由用户完成,对同一个ffrt_task_handle_t仅能调用一次`ffrt_task_handle_destroy`,重复对同一个ffrt_task_handle_t调用`ffrt_task_handle_destroy`,其行为是未定义的 1826* 在`ffrt_task_handle_destroy`之后再对ffrt_task_handle_t进行访问,其行为是未定义的 1827 1828#### 样例 1829 1830```{.c} 1831#include <stdio.h> 1832#include "ffrt.h" 1833 1834void func0(void* arg) 1835{ 1836 printf("hello "); 1837} 1838 1839void func1(void* arg) 1840{ 1841 (*(int*)arg)++; 1842} 1843 1844void func2(void* arg) 1845{ 1846 printf("world, x = %d\n", *(int*)arg); 1847} 1848 1849void func3(void* arg) 1850{ 1851 printf("handle wait"); 1852 (*(int*)arg)++; 1853} 1854 1855typedef struct { 1856 ffrt_function_header_t header; 1857 ffrt_function_t func; 1858 ffrt_function_t after_func; 1859 void* arg; 1860} c_function; 1861 1862static void ffrt_exec_function_wrapper(void* t) 1863{ 1864 c_function* f = (c_function*)t; 1865 if (f->func) { 1866 f->func(f->arg); 1867 } 1868} 1869 1870static void ffrt_destroy_function_wrapper(void* t) 1871{ 1872 c_function* f = (c_function*)t; 1873 if (f->after_func) { 1874 f->after_func(f->arg); 1875 } 1876} 1877 1878#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 1879static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 1880 const ffrt_function_t after_func, void* arg) 1881{ 1882 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 1883 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 1884 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1885 f->header.exec = ffrt_exec_function_wrapper; 1886 f->header.destroy = ffrt_destroy_function_wrapper; 1887 f->func = func; 1888 f->after_func = after_func; 1889 f->arg = arg; 1890 return (ffrt_function_header_t*)f; 1891} 1892 1893static inline ffrt_task_handle_t ffrt_submit_h_c(ffrt_function_t func, const ffrt_function_t after_func, 1894 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1895{ 1896 return ffrt_submit_h_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1897} 1898 1899static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 1900 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1901{ 1902 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1903} 1904 1905#define ffrt_deps_define(name, dep1, ...) const void* __v_##name[] = {dep1, ##__VA_ARGS__}; \ 1906 ffrt_deps_t name = {sizeof(__v_##name) / sizeof(void*), __v_##name} 1907 1908int main(int narg, char** argv) 1909{ 1910 // handle work with submit 1911 ffrt_task_handle_t h = ffrt_submit_h_c(func0, NULL, NULL, NULL, NULL, NULL); // not need some data in this task 1912 int x = 1; 1913 ffrt_deps_define(d1, &x); 1914 ffrt_deps_define(d2, &x, h); 1915 ffrt_submit_c(func1, NULL, &x, NULL, &d1, NULL); 1916 ffrt_submit_c(func2, NULL, &x, &d2, NULL, NULL); // this task depend x and h 1917 ffrt_task_handle_destroy(h); 1918 1919 // handle work with wait 1920 ffrt_task_handle_t h2 = ffrt_submit_h_c(func3, NULL, &x, NULL, NULL, NULL); 1921 ffrt_deps_define(d3, h2); 1922 ffrt_wait_deps(&d3); 1923 ffrt_task_handle_destroy(h2); 1924 printf("x = %d", x); 1925 ffrt_wait(); 1926 return 0; 1927} 1928``` 1929 1930* 预期的输出为 1931 1932``` 1933hello world, x = 2 1934handle wait 1935x = 3 1936``` 1937 1938 1939 1940### ffrt_this_task_get_id 1941 1942<hr/> 1943 1944* 返回当前task的id标识,更多使用用于维测(原因是task name可能重名) 1945 1946#### 声明 1947 1948```{.c} 1949uint64_t ffrt_this_task_get_id(); 1950``` 1951 1952#### 参数 1953 1954* 不涉及 1955 1956#### 返回值 1957 1958* 当前task的id 1959 1960#### 描述 1961 1962* 该接口在task内部调用将返回当前task的id标识,在task外部调用将返回0 1963* 可以基于该接口在task外部调用返回0的特性来区分函数是运行在FFRT 工作线程上还是非FFRT工作线程上 1964* task id为从1开始编码,每提交一个task便增加1,被设计成64bit,即便是每秒百万次提交,也需要292471.2年才会发生翻转 1965 1966#### 样例 1967 1968* 忽略 1969 1970 1971 1972### ffrt_this_task_update_qos 1973 1974<hr/> 1975 1976* 更新当前正在执行的task的优先级 1977 1978#### 声明 1979 1980```{.cpp} 1981int ffrt_this_task_update_qos(ffrt_qos_t qos); 1982``` 1983 1984#### 参数 1985 1986* `qos` 新的优先级 1987 1988#### 返回值 1989 1990* 0表示成功,非0表示失败 1991 1992#### 描述 1993 1994* 该接口对当前task的qos调整会立即生效 1995* 如果新设定的qos与当前的qos不一致,则会block当前task的执行,再按照新的qos恢复执行 1996* 如果新设定的qos与当前的qos一致,则接口会立即返回0,不做任何处理 1997* **如果在非task内部调用该接口,则返回非0值,用户可以选择忽略或其他处理** 1998 1999#### 样例 2000 2001* 忽略 2002 2003## 串行队列 2004<hr /> 2005* FFRT提供queue来实现Andorid中类似WorkQueue能力,且在使用得当的情况下将有更好的性能 2006 2007### ffrt_queue_attr_t 2008 2009#### 声明 2010```{.c} 2011typedef struct { 2012 uint32_t storage[(ffrt_queue_attr_storage_size + sizeof(uint32_t) - 1) / sizeof(uint32_t)]; 2013} ffrt_queue_attr_t; 2014 2015int ffrt_queue_attr_init(ffrt_queue_attr_t* attr); 2016void ffrt_queue_attr_destroy(ffrt_queue_attr_t* attr); 2017``` 2018 2019### 参数 2020 2021`attr` 2022* 该参数是指向未初始化的ffrt_queue_attr_t 2023 2024### 返回值 2025* 若成功返回0,否则返回-1 2026 2027### 描述 2028* ffrt_queue_attr_t用于创建ffrt_queue_t且不单独使用,因此必须在创建队列前先创建好队列属性 2029* ffrt_queue_attr_t对象的置空和销毁由用户完成,对同一个ffrt_queue_t仅能调用一次`ffrt_queue_attr_destroy`,重复对同一个ffrt_queue_t调用`ffrt_queue_attr_destroy`,其行为是未定义的 2030* 在`ffrt_queue_attr_destroy`之后再对ffrt_queue_t进行访问,其行为是未定义的 2031 2032### 样例 2033见[ffrt_queue_t](#ffrt_queue_t)章节的样例 2034 2035### ffrt_queue_t 2036<hr/> 2037 2038#### 声明 2039```{.c} 2040typedef enum { ffrt_queue_serial, ffrt_queue_max } ffrt_queue_type_t; 2041typedef void* ffrt_queue_t; 2042 2043ffrt_queue_t ffrt_queue_create(ffrt_queue_type_t type, const char* name, const ffrt_queue_attr_t* attr) 2044void ffrt_queue_destroy(ffrt_queue_t queue) 2045``` 2046 2047### 参数 2048 2049`type` 2050* 该参数用于描述创建的队列类型 2051 2052`name` 2053* 该参数用于描述创建队列的名字 2054 2055`attr` 2056* 该参数用于描述queue的属性,详见[ffrt_queue_attr_t](#ffrt_queue_attr_t)章节 2057 2058### 返回值 2059* 若成功则返回新创建的队列,否则返回空指针 2060 2061### 描述 2062* 提交至该队列的任务将按照顺序执行,如果某个提交的任务中发生阻塞,则无法保证该任务的执行顺序 2063* ffrt_queue_t对象的置空和销毁由用户完成,对同一个ffrt_queue_t仅能调用一次`ffrt_queue_t`,重复对同一个ffrt_queue_t调用`ffrt_queue_destroy`,其行为是未定义的 2064* 在`ffrt_queue_destroy`之后再对ffrt_queue_t进行访问,其行为是未定义的 2065 2066### 样例 2067``` 2068#include <stdio.h> 2069#include "ffrt.h" 2070 2071using namespace ffrt; 2072using namespace std; 2073 2074int main(int narg, char** argv) 2075{ 2076 ffrt_queue_attr_t queue_attr; 2077 (void)ffrt_queue_attr_init(&queue_attr); 2078 ffrt_queue_t queue_handle = ffrt_queue_create(ffrt_queue_serial, "test_queue", &queue_attr); 2079 ffrt_queue_attr_set_qos(&queue_attr, static_cast<int>(ffrt_qos_default)); 2080 int ret = ffrt_queue_attr_get_qos(&queue_attr); 2081 2082 ffrt_queue_submit(queue_handle, ffrt::create_function_wrapper([]() {printf("Task done.\n");}, ffrt_function_kind_queue), nullptr); 2083 2084 ffrt_queue_attr_destroy(&queue_attr); 2085 ffrt_queue_destroy(queue_handle); 2086} 2087``` 2088 2089### ffrt_queue_attr_init 2090<hr/> 2091 2092#### 声明 2093```{.c} 2094int ffrt_queue_attr_init(ffrt_queue_attr_t* attr); 2095``` 2096 2097### 参数 2098`attr` 2099* 该参数为已初始化的queue属性 2100 2101### 返回值 2102* 若成功返回0,否则返回-1 2103 2104### 描述 2105* 初始化串行队列的属性 2106 2107### 样例 2108* 见[ffrt_queue_t](#ffrt_queue_t)章节样例 2109 2110### ffrt_queue_attr_destroy 2111<hr/> 2112 2113#### 声明 2114```{.c} 2115void ffrt_queue_attr_destroy(ffrt_queue_attr_t* attr); 2116``` 2117 2118### 参数 2119`attr` 2120* 该参数为所创建的queue属性 2121 2122### 返回值 2123* 无返回值 2124 2125### 描述 2126* 销毁串行队列的属性 2127* 见[ffrt_queue_t](#ffrt_queue_t)章节的描述 2128 2129### 样例 2130* 见[ffrt_queue_t](#ffrt_queue_t)章节的样例 2131 2132 2133### ffrt_queue_attr_set_qos 2134<hr/> 2135 2136#### 声明 2137```{.c} 2138void ffrt_queue_attr_set_qos(ffrt_queue_attr_t* attr, ffrt_qos_t qos); 2139``` 2140 2141### 参数 2142`attr` 2143* 该参数为所创建的queue属性 2144 2145`qos` 2146* 该参数指向优先级QoS 2147 2148### 返回值 2149* 无返回值 2150 2151### 描述 2152* 设置串行队列qos属性 2153 2154### 样例 2155* 见[ffrt_queue_t](#ffrt_queue_t)章节的样例 2156 2157 2158### ffrt_queue_attr_get_qos 2159<hr/> 2160 2161#### 声明 2162```{.c} 2163ffrt_qos_t ffrt_queue_attr_get_qos(const ffrt_queue_attr_t* attr); 2164``` 2165 2166### 参数 2167`attr` 2168* 该参数为所创建的queue属性 2169 2170### 返回值 2171* 所设置的串行队列的qos等级,默认为default等级 2172 2173### 描述 2174* 获取串行队列qos属性 2175 2176### 样例 2177* 见[ffrt_queue_t](#ffrt_queue_t)章节的样例 2178 2179 2180### ffrt_queue_attr_set_timeout 2181<hr/> 2182 2183#### 声明 2184```{.c} 2185void ffrt_queue_attr_set_timeout(ffrt_queue_attr_t* attr, uint64_t timeout_us); 2186``` 2187 2188### 参数 2189`attr` 2190* 该参数为所创建的queue属性 2191 2192`timeout_us` 2193* 该参数用于描述串行队列任务执行超时时间,单位为us 2194 2195### 返回值 2196* 无返回值 2197 2198### 描述 2199* 设置串行队列任务执行超时时间 2200 2201### 样例 2202``` 2203#include <stdio.h> 2204#include "ffrt.h" 2205 2206using namespace ffrt; 2207using namespace std; 2208 2209int main(int narg, char** argv) 2210{ 2211 ffrt_queue_attr_t queue_attr; 2212 (void)ffrt_queue_attr_init(&queue_attr); 2213 ffrt_queue_attr_set_timeout(&queue_attr, 10000); 2214 uint64_t time = ffrt_queue_attr_get_timeout(&queue_attr); 2215 ffrt_queue_t queue_handle = ffrt_queue_create(ffrt_queue_serial, "test_queue", &queue_attr); 2216 2217 ffrt_queue_attr_destroy(&queue_attr); 2218 ffrt_queue_destroy(queue_handle); 2219} 2220``` 2221 2222 2223### ffrt_queue_attr_get_timeout 2224<hr/> 2225 2226#### 声明 2227```{.c} 2228uint64_t ffrt_queue_attr_get_timeout(const ffrt_queue_attr_t* attr); 2229``` 2230 2231### 参数 2232`attr` 2233* 该参数为所创建的queue属性 2234 2235### 返回值 2236* 串行队列任务执行超时时间,单位为us 2237 2238### 描述 2239* 获取所设的串行队列任务执行超时时间 2240 2241### 样例 2242* 见[ffrt_queue_attr_set_timeout](#ffrt_queue_attr_set_timeout)章节的样例 2243 2244 2245### ffrt_queue_attr_set_callback 2246<hr/> 2247 2248#### 声明 2249```{.c} 2250void ffrt_queue_attr_set_callback(ffrt_queue_attr_t* attr, ffrt_function_header_t* f); 2251``` 2252 2253### 参数 2254`attr` 2255* 该参数为所创建的queue属性 2256 2257`f` 2258* 该参数为串行队列超时回调函数 2259 2260### 返回值 2261* 无返回值 2262 2263### 描述 2264* 设置串行队列超时回调函数 2265 2266### 样例 2267``` 2268#include <stdio.h> 2269#include "ffrt.h" 2270 2271using namespace ffrt; 2272using namespace std; 2273 2274int main(int narg, char** argv) 2275{ 2276 ffrt_queue_attr_t queue_attr; 2277 (void)ffrt_queue_attr_init(&queue_attr); 2278 ffrt_queue_attr_set_callback(&queue_attr, ffrt::create_function_wrapper(cbOne, ffrt_function_kind_queue)); 2279 ffrt_function_header_t* func = ffrt_queue_attr_get_callback(nullptr); 2280 ffrt_queue_t queue_handle = ffrt_queue_create(ffrt_queue_serial, "test_queue", &queue_attr); 2281 2282 ffrt_queue_attr_destroy(&queue_attr); 2283 ffrt_queue_destroy(queue_handle); 2284} 2285``` 2286 2287 2288### ffrt_queue_attr_get_callback 2289<hr/> 2290 2291#### 声明 2292```{.c} 2293ffrt_function_header_t* ffrt_queue_attr_get_callback(const ffrt_queue_attr_t* attr); 2294``` 2295 2296### 参数 2297`attr` 2298* 该参数为所创建的queue属性 2299 2300### 返回值 2301* 串行队列任务超时回调函数 2302 2303### 描述 2304* 获取串行队列超时回调函数 2305 2306### 样例 2307* 见[ffrt_queue_attr_set_callback](#ffrt_queue_attr_set_callback)章节的样例 2308 2309 2310### ffrt_queue_create 2311<hr/> 2312 2313#### 声明 2314```{.c} 2315ffrt_queue_t ffrt_queue_create(ffrt_queue_type_t type, const char* name, const ffrt_queue_attr_t* attr); 2316``` 2317 2318### 参数 2319`type` 2320* 该参数用于描述创建的队列类型,串行队列type须为ffrt_queue_serial 2321 2322`name` 2323* 该参数用于描述创建的队列名称,若未设置则会默认设置为unnamed_... 2324 2325`attr` 2326* 该参数为所创建的queue属性,若未设定则会使用默认值 2327 2328### 返回值 2329* 如果成功创建了队列,则返回一个非空的队列句柄;否则返回空指针。 2330 2331### 描述 2332* 创建串行队列 2333 2334### 样例 2335* 见[ffrt_queue_t](#ffrt_queue_t)章节的样例 2336 2337 2338### ffrt_queue_destroy 2339<hr/> 2340 2341#### 声明 2342```{.c} 2343void ffrt_queue_destroy(ffrt_queue_t queue); 2344``` 2345 2346### 参数 2347`queue` 2348* 该参数为想要销毁的队列的句柄 2349 2350### 返回值 2351* 无 2352 2353### 描述 2354* 销毁串行队列 2355 2356### 样例 2357* 见[ffrt_queue_t](#ffrt_queue_t)章节的样例 2358 2359 2360### ffrt_queue_submit 2361<hr/> 2362 2363#### 声明 2364```{.c} 2365void ffrt_queue_submit(ffrt_queue_t queue, ffrt_function_header_t* f, const ffrt_task_attr_t* attr); 2366``` 2367 2368### 参数 2369`queue` 2370* 该参数为队列的句柄 2371 2372`f` 2373* 该参数为任务执行器指针 2374 2375`attr` 2376* 该参数为所创建的queue属性 2377 2378### 返回值 2379* 无 2380 2381### 描述 2382* 提交一个任务到队列中调度执行 2383 2384### 样例 2385* 见[ffrt_queue_t](#ffrt_queue_t)章节的样例 2386 2387 2388### ffrt_queue_submit_h 2389<hr/> 2390 2391#### 声明 2392```{.c} 2393ffrt_task_handle_t ffrt_queue_submit_h(ffrt_queue_t queue, ffrt_function_header_t* f, const ffrt_task_attr_t* attr); 2394``` 2395 2396### 参数 2397`queue` 2398* 该参数为队列的句柄 2399 2400`f` 2401* 该参数为任务执行器指针 2402 2403`attr` 2404* 该参数为所创建的queue属性 2405 2406### 返回值 2407* 如果任务被提交,则返回一个非空的任务句柄;否则返回空指针。 2408 2409### 描述 2410* 提交一个任务到队列中调度执行,并返回任务句柄 2411 2412### 样例 2413* 见[ffrt_queue_wait](#ffrt_queue_wait)章节的样例 2414 2415 2416### ffrt_queue_wait 2417<hr/> 2418 2419#### 声明 2420```{.c} 2421void ffrt_queue_wait(ffrt_task_handle_t handle); 2422``` 2423 2424### 参数 2425`handle` 2426* 该参数为任务的句柄 2427 2428### 返回值 2429* 无返回值 2430 2431### 描述 2432* 等待队列中一个任务执行完成 2433 2434### 样例 2435``` 2436#include <stdio.h> 2437#include "ffrt.h" 2438 2439using namespace ffrt; 2440using namespace std; 2441 2442int main(int narg, char** argv) 2443{ 2444 ffrt_queue_attr_t queue_attr; 2445 (void)ffrt_queue_attr_init(&queue_attr); 2446 ffrt_queue_t queue_handle = ffrt_queue_create(ffrt_queue_serial, "test_queue", &queue_attr); 2447 2448 std::function<void()>&& OnePlusFunc = [&result]() { OnePlusForTest((void *)(&result)); }; 2449 ffrt_task_handle_t task = ffrt_queue_submit_h(queue_handle, 2450 ffrt::create_function_wrapper(OnePlusFunc, ffrt_function_kind_queue), nullptr); 2451 ffrt_queue_wait(task); 2452 2453 ffrt_task_handle_destroy(task); 2454 ffrt_queue_attr_destroy(&queue_attr); 2455 ffrt_queue_destroy(queue_handle); 2456} 2457``` 2458 2459 2460 2461### ffrt_queue_cancel 2462<hr/> 2463 2464#### 声明 2465```{.c} 2466int ffrt_queue_cancel(ffrt_task_handle_t handle); 2467``` 2468 2469### 参数 2470`handle` 2471* 该参数为任务的句柄 2472 2473### 返回值 2474* 若成功返回0,否则返回-1 2475 2476### 描述 2477* 取消队列中一个任务 2478* 任务开始执行后则无法取消,仅能成功取消未开始执行的任务,若任务已开始执行会返回-1 2479* 不能通过队列名称取消任务,必须使用submit_h后拿到的task_handle,否则会报异常 2480 2481### 样例 2482``` 2483#include <stdio.h> 2484#include "ffrt.h" 2485 2486using namespace ffrt; 2487using namespace std; 2488 2489int main(int narg, char** argv) 2490{ 2491 ffrt_queue_attr_t queue_attr; 2492 (void)ffrt_queue_attr_init(&queue_attr); 2493 ffrt_queue_t queue_handle = ffrt_queue_create(ffrt_queue_serial, "test_queue", &queue_attr); 2494 2495 std::function<void()>&& OnePlusFunc = [&result]() { OnePlusForTest((void *)(&result)); }; 2496 ffrt_task_handle_t task = ffrt_queue_submit_h(queue_handle, 2497 ffrt::create_function_wrapper(OnePlusFunc, ffrt_function_kind_queue), nullptr); 2498 int ret = ffrt_queue_cancel(task); 2499 2500 ffrt_task_attr_destroy(&task_attr); 2501 ffrt_task_handle_destroy(task); 2502 ffrt_queue_attr_destroy(&queue_attr); 2503 ffrt_queue_destroy(queue_handle); 2504} 2505``` 2506 2507 2508## 同步原语 2509 2510### ffrt_mutex_t 2511<hr/> 2512* FFRT提供的类似pthread mutex 的性能实现 2513 2514#### 声明 2515 2516```{.cpp} 2517typedef enum { 2518 ffrt_error = -1, 2519 ffrt_success = 0, 2520 ffrt_error_nomem = ENOMEM, 2521 ffrt_error_timedout = ETIMEDOUT, 2522 ffrt_error_busy = EBUSY, 2523 ffrt_error_inval = EINVAL 2524} ffrt_error_t; 2525 2526struct ffrt_mutex_t; 2527 2528int ffrt_mutex_init(ffrt_mutex_t* mutex, const ffrt_mutexattr_t* attr); 2529int ffrt_mutex_lock(ffrt_mutex_t* mutex); 2530int ffrt_mutex_unlock(ffrt_mutex_t* mutex); 2531int ffrt_mutex_trylock(ffrt_mutex_t* mutex); 2532int ffrt_mutex_destroy(ffrt_mutex_t* mutex); 2533``` 2534 2535#### 参数 2536 2537`attr` 2538 2539* 当前FFRT只支持基础类型的mutex,因此attr必须为空指针 2540 2541`mutex` 2542 2543* 指向所操作的互斥锁的指针 2544 2545#### 返回值 2546 2547* 若成功则为 ffrt_success ,否则发生错误 2548 2549#### 描述 2550* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 2551* 该功能能够避免pthread传统的pthread_mutex_t 在抢不到锁时陷入内核的问题,在使用得当的条件下将会有更好的性能 2552* **注意:目前暂不支持递归和定时功能** 2553* **注意:C API中的ffrt_mutex_t需要用户调用`ffrt_mutex_init`和`ffrt_mutex_destroy`显式创建和销毁,而C++ API无需该操作** 2554* **注意:C API中的ffrt_mutex_t对象的置空和销毁由用户完成,对同一个ffrt_mutex_t仅能调用一次`ffrt_mutex_destroy`,重复对同一个ffrt_mutex_t调用`ffrt_mutex_destroy`,其行为是未定义的** 2555* **注意:在`ffrt_mutex_destroy`之后再对ffrt_mutex_t进行访问,其行为是未定义的** 2556 2557#### 样例 2558 2559```{.c} 2560#include <stdio.h> 2561#include "ffrt.h" 2562 2563typedef struct { 2564 int* sum; 2565 ffrt_mutex_t* mtx; 2566} tuple; 2567 2568void func(void* arg) 2569{ 2570 tuple* t = (tuple*)arg; 2571 2572 int ret = ffrt_mutex_lock(t->mtx); 2573 if (ret != ffrt_success) { 2574 printf("error\n"); 2575 } 2576 (*t->sum)++; 2577 ret = ffrt_mutex_unlock(t->mtx); 2578 if (ret != ffrt_success) { 2579 printf("error\n"); 2580 } 2581} 2582 2583typedef struct { 2584 ffrt_function_header_t header; 2585 ffrt_function_t func; 2586 ffrt_function_t after_func; 2587 void* arg; 2588} c_function; 2589 2590static void ffrt_exec_function_wrapper(void* t) 2591{ 2592 c_function* f = (c_function*)t; 2593 if (f->func) { 2594 f->func(f->arg); 2595 } 2596} 2597 2598static void ffrt_destroy_function_wrapper(void* t) 2599{ 2600 c_function* f = (c_function*)t; 2601 if (f->after_func) { 2602 f->after_func(f->arg); 2603 } 2604} 2605 2606#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 2607static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 2608 const ffrt_function_t after_func, void* arg) 2609{ 2610 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 2611 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 2612 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 2613 f->header.exec = ffrt_exec_function_wrapper; 2614 f->header.destroy = ffrt_destroy_function_wrapper; 2615 f->func = func; 2616 f->after_func = after_func; 2617 f->arg = arg; 2618 return (ffrt_function_header_t*)f; 2619} 2620 2621static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 2622 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 2623{ 2624 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 2625} 2626 2627void ffrt_mutex_task() 2628{ 2629 int sum = 0; 2630 ffrt_mutex_t mtx; 2631 tuple t = {&sum, &mtx}; 2632 int ret = ffrt_mutex_init(&mtx, NULL); 2633 if (ret != ffrt_success) { 2634 printf("error\n"); 2635 } 2636 for (int i = 0; i < 10; i++) { 2637 ffrt_submit_c(func, NULL, &t, NULL, NULL, NULL); 2638 } 2639 ffrt_mutex_destroy(&mtx); 2640 ffrt_wait(); 2641 printf("sum = %d", sum); 2642} 2643 2644int main(int narg, char** argv) 2645{ 2646 int r; 2647 ffrt_submit_c(ffrt_mutex_task, NULL, NULL, NULL, NULL, NULL); 2648 ffrt_wait(); 2649 return 0; 2650} 2651``` 2652 2653预期输出为 2654 2655``` 2656sum=10 2657``` 2658 2659* 该例子为功能示例,实际中并不鼓励这样使用 2660 2661 2662### ffrt_cond_t 2663<hr/> 2664 2665* FFRT提供的类似pthread 信号量的性能实现 2666 2667#### 声明 2668 2669```{.c} 2670typedef enum { 2671 ffrt_error = -1, 2672 ffrt_success = 0, 2673 ffrt_error_nomem = ENOMEM, 2674 ffrt_error_timedout = ETIMEDOUT, 2675 ffrt_error_busy = EBUSY, 2676 ffrt_error_inval = EINVAL 2677} ffrt_error_t; 2678 2679struct ffrt_cond_t; 2680typedef enum { 2681 ffrt_clock_realtime = CLOCK_REALTIME, 2682 ffrt_clock_monotonic = CLOCK_MONOTONIC 2683} ffrt_clockid_t; 2684 2685int ffrt_condattr_init(ffrt_condattr_t* attr); 2686int ffrt_condattr_destroy(ffrt_condattr_t* attr); 2687int ffrt_condattr_setclock(ffrt_condattr_t* attr, ffrt_clockid_t clock); 2688int ffrt_condattr_getclock(const ffrt_condattr_t* attr, ffrt_clockid_t* clock); 2689 2690int ffrt_cond_init(ffrt_cond_t* cond, const ffrt_condattr_t* attr); 2691int ffrt_cond_signal(ffrt_cond_t* cond); 2692int ffrt_cond_broadcast(ffrt_cond_t* cond); 2693int ffrt_cond_wait(ffrt_cond_t*cond, ffrt_mutex_t* mutex); 2694int ffrt_cond_timedwait(ffrt_cond_t* cond, ffrt_mutex_t* mutex, const struct timespec* time_point); 2695int ffrt_cond_destroy(ffrt_cond_t* cond); 2696``` 2697 2698#### 参数 2699 2700`cond` 2701 2702* 指向所操作的信号量的指针 2703 2704`attr` 2705 2706* 属性设定,空指针表示使用默认属性 2707 2708`mutex` 2709 2710* 指向要在阻塞期间解锁的互斥锁的指针 2711 2712`time_point` 2713 2714* 指向指定等待时限时间的对象的指针 2715 2716 2717#### 返回值 2718 2719* 若成功则为 ffrt_success,若在锁定互斥前抵达时限则为 ffrt_error_timedout 2720 2721#### 描述 2722* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 2723* 该功能能够避免传统的pthread_cond_t在条件不满足时陷入内核的问题,在使用得当的条件下将会有更好的性能 2724* **注意:C API中的ffrt_cond_t需要用户调用`ffrt_cond_init`和`ffrt_cond_destroy`显式创建和销毁,而C++ API中依赖构造和析构自动完成** 2725* **注意:C API中的ffrt_cond_t对象的置空和销毁由用户完成,对同一个ffrt_cond_t仅能调用一次`ffrt_cond_destroy`,重复对同一个ffrt_cond_t调用`ffrt_cond_destroy`,其行为是未定义的** 2726* **注意:在`ffrt_cond_destroy`之后再对ffrt_cond_t进行访问,其行为是未定义的** 2727 2728#### 样例 2729 2730```{.c} 2731#include <stdio.h> 2732#include "ffrt.h" 2733 2734typedef struct { 2735 ffrt_cond_t* cond; 2736 int* a; 2737 ffrt_mutex_t* lock_; 2738} tuple; 2739 2740void func1(void* arg) 2741{ 2742 tuple* t = (tuple*)arg; 2743 int ret = ffrt_mutex_lock(t->lock_); 2744 if (ret != ffrt_success) { 2745 printf("error\n"); 2746 } 2747 while (*t->a != 1) { 2748 ret = ffrt_cond_wait(t->cond, t->lock_); 2749 if (ret != ffrt_success) { 2750 printf("error\n"); 2751 } 2752 } 2753 ret = ffrt_mutex_unlock(t->lock_); 2754 if (ret != ffrt_success) { 2755 printf("error\n"); 2756 } 2757 printf("a = %d", *(t->a)); 2758} 2759 2760void func2(void* arg) 2761{ 2762 tuple* t = (tuple*)arg; 2763 int ret = ffrt_mutex_lock(t->lock_); 2764 if (ret != ffrt_success) { 2765 printf("error\n"); 2766 } 2767 *(t->a) = 1; 2768 ret = ffrt_cond_signal(t->cond); 2769 if (ret != ffrt_success) { 2770 printf("error\n"); 2771 } 2772 ret = ffrt_mutex_unlock(t->lock_); 2773 if (ret != ffrt_success) { 2774 printf("error\n"); 2775 } 2776} 2777 2778typedef struct { 2779 ffrt_function_header_t header; 2780 ffrt_function_t func; 2781 ffrt_function_t after_func; 2782 void* arg; 2783} c_function; 2784 2785static void ffrt_exec_function_wrapper(void* t) 2786{ 2787 c_function* f = (c_function*)t; 2788 if (f->func) { 2789 f->func(f->arg); 2790 } 2791} 2792 2793static void ffrt_destroy_function_wrapper(void* t) 2794{ 2795 c_function* f = (c_function*)t; 2796 if (f->after_func) { 2797 f->after_func(f->arg); 2798 } 2799} 2800 2801#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 2802static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 2803 const ffrt_function_t after_func, void* arg) 2804{ 2805 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 2806 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 2807 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 2808 f->header.exec = ffrt_exec_function_wrapper; 2809 f->header.destroy = ffrt_destroy_function_wrapper; 2810 f->func = func; 2811 f->after_func = after_func; 2812 f->arg = arg; 2813 return (ffrt_function_header_t*)f; 2814} 2815 2816static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 2817 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 2818{ 2819 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 2820} 2821 2822void ffrt_cv_task() 2823{ 2824 ffrt_cond_t cond; 2825 int ret = ffrt_cond_init(&cond, NULL); 2826 if (ret != ffrt_success) { 2827 printf("error\n"); 2828 } 2829 int a = 0; 2830 ffrt_mutex_t lock_; 2831 tuple t = {&cond, &a, &lock_}; 2832 ret = ffrt_mutex_init(&lock_, NULL); 2833 if (ret != ffrt_success) { 2834 printf("error\n"); 2835 } 2836 ffrt_submit_c(func1, NULL, &t, NULL, NULL, NULL); 2837 ffrt_submit_c(func2, NULL, &t, NULL, NULL, NULL); 2838 ffrt_wait(); 2839 ffrt_cond_destroy(&cond); 2840 ffrt_mutex_destroy(&lock_); 2841} 2842 2843int main(int narg, char** argv) 2844{ 2845 ffrt_submit_c(ffrt_cv_task, NULL, NULL, NULL, NULL, NULL); 2846 ffrt_wait(); 2847 return 0; 2848} 2849``` 2850 2851预期输出为: 2852 2853``` 2854a=1 2855``` 2856 2857* 该例子为功能示例,实际中并不鼓励这样使用 2858 2859## 杂项 2860 2861### ffrt_usleep 2862 2863<hr/> 2864* FFRT提供的类似C11 sleep和linux usleep的性能实现 2865 2866#### 声明 2867 2868```{.c} 2869int ffrt_usleep(uint64_t usec); 2870``` 2871 2872#### 参数 2873 2874`usec` 2875 2876* 睡眠的us数 2877 2878#### 返回值 2879 2880* 不涉及 2881 2882#### 描述 2883* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 2884* 该功能能够避免传统的sleep 睡眠时陷入内核的问题,在使用得当的条件下将会有更好的性能 2885 2886#### 样例 2887 2888```{.c} 2889#include <time.h> 2890#include <stdio.h> 2891#include "ffrt.h" 2892 2893void func(void* arg) 2894{ 2895 printf("Time: %s", ctime(&(time_t){time(NULL)})); 2896 ffrt_usleep(2000000); // 睡眠 2 秒 2897 printf("Time: %s", ctime(&(time_t){time(NULL)})); 2898} 2899 2900typedef struct { 2901 ffrt_function_header_t header; 2902 ffrt_function_t func; 2903 ffrt_function_t after_func; 2904 void* arg; 2905} c_function; 2906 2907static void ffrt_exec_function_wrapper(void* t) 2908{ 2909 c_function* f = (c_function*)t; 2910 if (f->func) { 2911 f->func(f->arg); 2912 } 2913} 2914 2915static void ffrt_destroy_function_wrapper(void* t) 2916{ 2917 c_function* f = (c_function*)t; 2918 if (f->after_func) { 2919 f->after_func(f->arg); 2920 } 2921} 2922 2923#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 2924static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 2925 const ffrt_function_t after_func, void* arg) 2926{ 2927 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 2928 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 2929 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 2930 f->header.exec = ffrt_exec_function_wrapper; 2931 f->header.destroy = ffrt_destroy_function_wrapper; 2932 f->func = func; 2933 f->after_func = after_func; 2934 f->arg = arg; 2935 return (ffrt_function_header_t*)f; 2936} 2937 2938static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 2939 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 2940{ 2941 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 2942} 2943 2944int main(int narg, char** argv) 2945{ 2946 ffrt_submit_c(func, NULL, NULL, NULL, NULL, NULL); 2947 ffrt_wait(); 2948 return 0; 2949} 2950``` 2951 2952### ffrt_yield 2953<hr/> 2954* 当前task 主动让出CPU 执行资源,让其他可以被执行的task 先执行,如果没有其他可被执行的task,yield 无效 2955 2956#### 声明 2957 2958```{.cpp} 2959void ffrt_yield(); 2960``` 2961 2962#### 参数 2963 2964* 不涉及 2965 2966#### 返回值 2967 2968* 不涉及 2969 2970#### 描述 2971* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 2972* 此函数的确切行为取决于实现,特别是使用中的FFRT 调度程序的机制和系统状态 2973 2974#### 样例 2975 2976* 省略 2977 2978 2979# 部署 2980 2981## 部署方式 2982<img src="images/image-20230120153923679.png" alt="image-20230120153923679" style="zoom:67%;" /> 2983 2984* FFRT的部署依赖FFRT动态库libffrt.so和一组header头文件 2985 2986* FFRT的头文件为`ffrt.h`,内部包含了C++ API,C API和C base API 2987 * ffrt.h 定义为: 2988 ```{.cpp} 2989 #ifndef FFRT_API_FFRT_H 2990 #define FFRT_API_FFRT_H 2991 #ifdef __cplusplus 2992 #include "cpp/task.h" 2993 #include "cpp/deadline.h" 2994 #include "cpp/sys_event.h" 2995 #include "cpp/mutex.h" 2996 #include "cpp/condition_variable.h" 2997 #include "cpp/sleep.h" 2998 #include "cpp/thread.h" 2999 #include "cpp/config.h" 3000 #include "cpp/future.h" 3001 #else 3002 #include "c/task.h" 3003 #include "c/deadline.h" 3004 #include "c/sys_event.h" 3005 #include "c/mutex.h" 3006 #include "c/condition_variable.h" 3007 #include "c/sleep.h" 3008 #include "c/thread.h" 3009 #include "c/config.h" 3010 #endif 3011 #endif 3012 ``` 3013 * C base API定义示例: 3014 ```{.cpp} 3015 void ffrt_submit_base(ffrt_function_header_t* func, ...); 3016 int ffrt_mutex_init(...); 3017 ``` 3018 * C API定义示例: 3019 ```{.cpp} 3020 static inline void ffrt_submit(ffrt_function_t func, void* arg, ...) 3021 { 3022 ffrt_submit_base(ffrt_create_function_wrapper(func, arg), ...); 3023 } 3024 ``` 3025 * C++ API定义示例: 3026 ```{.cpp} 3027 namespace ffrt { 3028 static inline void submit(std::function& func, ...) 3029 { 3030 ffrt_submit_base(ffrt_create_function_wrapper(func), ...); 3031 } 3032 struct mutex { 3033 mutex() { 3034 ffrt_mutex_init(...); 3035 ... 3036 }; 3037 } 3038 ``` 3039 3040* **出于易用性方面的考虑,除非必要,强烈建议你使用C++ API,调用C API将会使你的代码非常臃肿或者更容易产生资源未释放问题** 3041 3042| 需求列表 | 3043| ------------------------------------------------------------ | 3044| 需求1:ABI兼容性,在NDK场景中由于用户的编译环境与FFRT的编译环境不同,使用C++接口可能存在ABI兼容性问题,要有解决方案 | 3045| 需求2:用户的编译环境为纯C编译环境,不想因为引入FFRT而引入C++元素的场景,要有解决方案 | 3046| 需求3:易用性,尽可能让接口简单易用,用户少出错 | 3047 3048* 对于需求1,通过在用户调用的C++接口和FFRT的实现之间增加一个C base API层,并基于头文件方式将API中的C++的元素编译到用户的so,从而解决ABI兼容的问题 3049* 对于需求2,可以通过C Base API解决 3050* 对于需求3,建议用户尽可能使用C++ API,以避免C API固有的资源未初始化/释放、参数冗长等问题,对于不得不使用C API的场景,FFRT仍然支持用户使用C API和C base API 3051 3052 3053 3054<br/> 3055<br/> 3056 3057<hr/> 3058# 实战指南 3059 3060## 步骤1: 分析应用 3061 3062使用 FFRT 并行编程的第一步便是您需要了解你的应用。 3063 3064【建议1】:使用 Task 梳理应用的流程。 3065 3066使用 Task 梳理应用的流程,并且尽可能使用数据来表达 Task 之间的依赖。当然如果两个 Task 之间如无数据依赖,仅存在控制依赖,您也可以创建一个虚拟的(或者逻辑上的)数据依赖。 3067 3068<img src="images/image-20220926152831526.png" style="zoom:70%" /> 3069 3070<center>AIRAW 的数据流图</center> 3071 3072基于数据流图,可以很容易判定出哪些 Task 是可以并发的,比如,Slice0 的 NPU Task 和 Slice1 的 GPU Pre Task 是可以并发的,因为它们没有任何依赖。 3073 3074反过来,如果并发的效果不理想,也可以通过调整数据流图来优化并发。例如,假如上图中GPU Pre Task 执行时间有很大波动,但平均耗时略小于 NPU Task,会出现某些时刻 GPU Pre Task 拖慢整个执行时间。此时,如果将 GPU Pre Task 的输出 Buffer 改成3个(或者更多)的 Buffer ,可以增加 GPU Pre Task 和 NPU Task 的并发机会,将降低波动对总执行时间的影响。 3075 3076 3077 3078【建议2】:这里不用太担心 Task 大或小的问题,因为 FFRT 允许你在 Task 内部继续拆分 SubTask,可以逐步细化。 3079 3080下图中,第一次画数据流图时,可以不将 FaceDirection 和 UpdateExistFaceImageInfo 两个 Task 展开,可以逐步细化。 3081 3082<img src="images/image-20220926153003884.png" style="zoom:70%" /> 3083 3084<center>某拍照业务的数据流图</center> 3085 3086【建议3】:上述流程图或者数据流图不要求是静态图(即 Task 数量和 Task 依赖关系是固定的) 3087 3088FFRT 允许动态提交 Task ,在编程界面上不体现图的概念,FFRT 内部会根据Task 之间的依赖关系动态调整数据流图的节点。 3089 3090 3091【建议4】:尽可能对应用做热点分析 3092 3093如果是对存量代码的 FFRT 化改造,那么,使用 System Trace 这类工具能帮助您聚焦在性能热点上,比如下图可以很容易知道当前的性能Bound,在分析数据流图时,可以重点关注这些热点任务。 3094 3095<img src="images/image-20220926153030993.png" style="zoom:70%" /> 3096 3097<center>某业务的System Trace</center> 3098 3099## 步骤2: 并行化应用 3100 3101【建议1】:不要直接使用线程,使用 FFRT 提交Task。 3102 3103如果应用中有明显的数据依赖关系,那么 FFRT 将会非常适合;最差的情况是应用没有数据依赖或难以并行(如果真的存在),您仍然可以把 FFRT 当做一个高效的进程级线程池、或者协程库去使用它,但非常不建议你继续创建线程。 3104 3105 3106 3107【建议2】:Task 最好被建模为纯函数。 3108 3109纯函数是指其执行没有副作用,例如更新全局数据结构。每个任务都依赖于其输入/输出签名来连接到其他任务。 3110 3111请注意,即使 Task 不是"纯"的,FFRT 仍然适用。只要任务使用的数据依赖或者锁足以保证正确执行,FFRT 就能正常工作。 3112 3113 3114 3115【建议3】:尽可能尝试通过 inDeps/outDeps 表达依赖,而不是使用 ffrt::wait()。 3116 3117这是因为 FFRT 跟踪和处理 inDeps/outDeps 比调用显式 ffrt::wait() 函数更自然、更便宜。 3118 3119 3120 3121【建议4】:注意 Task 粒度 3122 3123以适当的粒度提交任务至关重要:目前每个任务的调度开销约为 10 us。如果 Task 的粒度非常小,那么开销的百分比将会很高。FFRT 会继续基于软硬件的方式优化调度开销。 3124 3125 3126 3127【建议5】:尽可能使用 FFRT 原语 3128 3129如果需要mutex、sleep、异步 I/O,请使用 FFRT 原语,而不是使用OS 提供的版本。因为这些 FFRT 提供的实现在与 FFRT 配合时开销将会更小。 3130 3131 3132 3133【建议6】:在需要时,使用 ffrt::wait() 确保栈变量的生命周期。 3134 3135如果子任务使用驻留在父任务栈上的数据,则父任务应避免在子任务执行完成前返回。在父任务的末尾添加 ffrt::wait() 可以解决这个问题。 3136 3137 3138 3139## 步骤3: 优化应用 3140 3141【建议1】:基于System Trace,分析并行是否符合预期 3142 3143FFRT 已经内置 SysTrace 支持,默认以txx.xx表示,非常有利于分析 Task 粒度和并发度。未来,在性能分析和维测方面将继续增强。 3144 3145<img src="images/image-20220926153209875.png" style="zoom:70%" /> 3146 3147【建议2】:对于耗时的 Task,尝试提交 SubTask,提升应用的并行度 3148 3149 3150【建议3】:在合适的场景,使用 Deadline 调度,实现能效和性能的平衡 3151 3152方案正在验证中,待更新。 3153 3154 3155 3156## 样例: CameraHal QuickThumb 3157 3158### 步骤1: 分析应用 3159 3160<img src="images/image-20220926153255824.png" style="zoom:70%" /> 3161 31621) QuickThumb 是 CameraHal 中实现的对一张图片进行缩小的功能,整体运行时间约30 us; 3163 31642) 在实现上分为两层循环,外层的一次循环输出1行,内层的1次循环输出该行的m列; 3165 31663) 在划分 Task 时,一种简单的做法是1行的处理就是1个Task。 3167 3168### 步骤2: 并行化应用 3169<img src="images/image-20220926153509205.png" style="zoom:100%" /> 3170 3171 1) 根据纯函数的定义,该 Task 的输入输出的数据是非常之多的,因此,这个场景下使用更宽泛的纯函数的定义,只需要考虑在 Task 内部会被写,但是却被定义在 Task 外部的变量即可; 3172 31732) 按照上面的原则,将 py/puv 的定义移到 Task 内部可避免多个 Task 同时写 py/puv 的问题; 3174 31753) s32r 的处理可以有两种方式,都能得到正确的功能:a. 保持定义在Task 外部,但作为Task 的输出依赖;b. 将s32r定义在Task 内部,作为Task的私有变量。显然,b 方案能够获得更好的性能 3176 3177 3178 3179### 步骤3: 优化应用 3180 3181通过System Trace,会发现上述改造方案的Task 粒度较小,大约单个Task 耗时在5us左右,因此,扩大Task的粒度为32行处理,得到最终的并行结果,下图为使用4小核和3中核的结果。 3182 3183<img src="images/image-20220926153603572.png" style="zoom:100%" /> 3184 3185 3186 3187## 样例: Camera AIRAW 3188 3189### 步骤1: 分析应用 3190<img src="images/image-20220926153611121.png" style="zoom:70%" /> 3191 3192AIRAW 的处理包括了3个处理步骤,在数据面上,可以按slice 进行切分,在不考虑pre_outbuf和npu_outbuf 在slice间复用的情况下,数据流图如上图所示。 3193 3194<img src="images/image-20220926152831526.png" style="zoom:70%" /> 3195 3196为了节省运行过程中的内存占用,但不影响整体性能,可以只保留2个pre_outbuf和2个npu_outbuf。 3197 3198`为此付出的代价是:Buffer 的复用产生了slice3 的GPU Pre Task 依赖slice1 的NPU Task 完成,俗称反压,又称生产者依赖关系。但是,如果您使用 FFRT 来实现,将会是非常自然而高效的` 3199 3200### 步骤2: 并行化应用 3201 3202```{.cpp} 3203constexpr uint32_t SLICE_NUM = 24; 3204constexpr uint32_t BUFFER_NUM = 2; 3205 3206int input[SLICE_NUM]; // input is split into SLICE_NUM slices 3207int pre_outbuf[BUFFER_NUM]; // gpu pre task output buffers 3208int npu_outbuf[BUFFER_NUM]; // npu output buffers 3209int output[SLICE_NUM]; // output is split into SLICE_NUM slices 3210 3211for (uint32_t i = 0; i < SLICE_NUM; i++) { 3212 uint32_t buf_id = i % BUFFER_NUM; 3213 ffrt::submit(gpuPreTask, {input + i}, {pre_outbuf + buf_id}); 3214 ffrt::submit(npuTask, {pre_outbuf + buf_id}, {npu_outbuf + buf_id}); 3215 ffrt::submit(gpuPostTask, {npu_outbuf + buf_id}, {output + i}); 3216} 3217 3218ffrt::wait(); 3219``` 3220 3221### 步骤3: 优化应用 3222 3223<img src="images/image-20220926153825527.png" style="zoom:100%" /> 3224 3225基于以上实现,从上面的Trace中我们看到,NPU 的硬件时间被完全用满,系统端到端性能达到最优,而付出的开发代价将会比GCD 或多线程小的多。 3226 3227 3228 3229## 样例: Camera FaceStory 3230 3231### 步骤1: 分析应用 3232 3233<img src="images/image-20220926153003884.png" style="zoom:70%" /> 3234 3235 3236 3237### 步骤2: 并行化应用 3238 3239<img src="images/image-20220926153906692.png" style="zoom:100%" /> 3240 3241代码改造样例 3242 32431) 该场景输出存量代码迁移,只需将原先串行的代码以Task的方式提交即可; 3244 32452) 过程中需要考虑Data Race和数据生命周期; 3246 32473) 先提交大的Task,根据需要逐步拆分SubTask。 3248 3249 3250 3251### 步骤3: 优化应用 3252 3253<img src="images/image-20220926153030993.png" style="zoom:100%" /> 3254 3255<center>原始System Trace</center> 3256 3257<img src="images/image-20220926153925963.png" style="zoom:100%" /> 3258 3259 3260 3261 3262<center>改造后System Trace</center> 3263 3264并行化的收益来自于: 3265 32661) 多分支或循环并发,实现CPU前后处理和NPU的并发 3267 32682) 子任务拆分,进一步提升并行度 3269 32703) 基于数据流图优化CPU L2 Cache Flush频次 3271 32724) NPU Worker Thread实时优先级调整,后续FFRT中考虑独立出XPU调度Worker来保证实时性 3273 32745) 在未来,模型加载使用FFRT submit,模型加载内部也可以使用submit来继续拆分,能够优化整个业务的启动耗时。 3275 3276<br/> 3277<br/> 3278<hr/> 3279 3280 3281 3282# 使用建议 3283 3284## 建议1: 函数化 3285 3286**基本思想:计算过程函数化** 3287 3288* 程序过程各步骤以函数封装表达,函数满足类纯函数特性 3289* 无全局数据访问 3290* 无内部状态保留 3291* 通过ffrt::submit()接口以异步任务方式提交函数执行 3292* 将函数访问的数据对象以及访问方式在ffrt::submit()接口中的in_deps/out_deps参数表达 3293* 程序员通过inDeps/outDeps参数表达任务间依赖关系以保证程序执行的正确性 3294 3295> 做到纯函数的好处在于:1. 能够最大化挖掘并行度,2.避免DataRace和锁的问题 3296 3297 3298 3299**在实际中,可以根据场景放松纯函数的约束,但前提是:** 3300 3301* 确定添加的in_deps/out_deps可确保程序正确执行 3302* 通过FFRT提供的锁机制保护对全局变量的访问 3303 3304 3305 3306## 建议2: 注意任务粒度 3307 3308* FFRT管理和调度异步任务执行有调度开销,任务粒度(执行时间)需匹配调度开销 3309* 大量小粒度任务造成FFRT调度开销占比增加,性能下降,解决方法: 3310 * 将多个小粒度任务聚合为大粒度任务一次发送给FFRT异步执行 3311 * 同步方式执行小粒度任务,不发送给FFRT异步执行。需注意和异步任务之间的数据同步问题,在需要同步的地方插入ffrt::wait() 3312 * 下面的例子中,fib_ffrt2会比fib_ffrt1拥有更好的性能 3313 3314 ```{.cpp} 3315 #include "ffrt.h" 3316 void fib_ffrt1(int x, int& y) 3317 { 3318 if (x <= 1) { 3319 y = x; 3320 } else { 3321 int y1, y2; 3322 ffrt::submit([&] {fib_ffrt1(x - 1, y1);}, {&x}, {&y1} ); 3323 ffrt::submit([&] {fib_ffrt1(x - 2, y2);}, {&x}, {&y2} ); 3324 ffrt::submit([&] {y = y1 + y2;}, {&y1, &y2}, {} ); 3325 ffrt::wait(); 3326 } 3327 } 3328 3329 void fib_ffrt2(int x, int& y) 3330 { 3331 if (x <= 1) { 3332 y = x; 3333 } else { 3334 int y1, y2; 3335 ffrt::submit([&] {fib_ffrt2(x - 1, y1);}, {&x}, {&y1} ); 3336 ffrt::submit([&] {fib_ffrt2(x - 2, y2);}, {&x}, {&y2} ); 3337 ffrt::wait({&y1, &y2}); 3338 y = y1 + y2; 3339 } 3340 } 3341 ``` 3342 3343 3344 3345## 建议3: 数据生命周期 3346 3347* FFRT的任务提交和执行是异步的,因此需要确保任务执行时,对任务中涉及的数据的访问是有效的 3348* 常见问题:子任务使用父任务栈数据,当父任务先于子任务执行完成时释放栈数据,子任务产生数据访问错误 3349* 解决方法1:父任务中增加ffrt::wait()等待子任务完成 3350 3351```{.cpp} 3352#include "ffrt.h" 3353void fib_ffrt(int x, int& y) 3354{ 3355 if (x <= 1) { 3356 y = x; 3357 } else { 3358 int y1, y2; 3359 ffrt::submit([&] {fib_ffrt(x - 1, y1);}, {&x}, {&y1} ); 3360 ffrt::submit([&] {fib_ffrt(x - 2, y2);}, {&x}, {&y2} ); 3361 ffrt::submit([&] {y = y1 + y2;}, {&y1, &y2}, {} ); 3362 ffrt::wait(); // 用于保证y1 y2的生命周期 3363 } 3364} 3365``` 3366 3367* 解决方法2:将数据由栈移到堆,手动管理生命周期 3368 3369```{.cpp} 3370#include "ffrt.h" 3371void fib_ffrt(int x, int* y) 3372{ 3373 if (x <= 1) { 3374 *y = x; 3375 } else { 3376 int *y1 = (int*)malloc(sizeof(int)); 3377 int *y2 = (int*)malloc(sizeof(int)); 3378 3379 ffrt::submit([=] {fib_ffrt(x - 1, y1);}, {}, {y1} ); 3380 ffrt::submit([=] {fib_ffrt(x - 2, y2);}, {}, {y2} ); 3381 ffrt::submit([=] {*y = *y1 + *y2; }, {y1, y2}, {} ); 3382 ffrt::wait(); 3383 } 3384} 3385``` 3386 3387 3388 3389## 建议4: 使用FFRT提供的替代API 3390 3391* 禁止在FFRT任务中使用系统线程库API创建线程,使用submit提交任务 3392* 使用FFRT提供的锁,条件变量,睡眠,IO等API代替系统线程库API 3393 * 使用系统线程库API可能造成工作线程阻塞,引起额外性能开销 3394 3395 3396 3397## 建议5: Deadline机制 3398 3399* **必须用于具备周期/重复执行特征的处理流程** 3400* 在有明确时间约束和性能关键的处理流程中使用,避免滥用 3401* 在相对大颗粒度的处理流程中使用,例如具有16.6ms时间约束的帧处理流程 3402 3403 3404 3405## 建议6: 从线程模型迁移 3406 3407* 创建线程替代为创建FFRT任务 3408 * 线程从逻辑上类似无in_deps的任务 3409* 识别线程间的依赖关系,并将其表达在任务的依赖关系in_deps/out_deps上 3410* 线程内计算过程分解为异步任务调用 3411* 通过任务依赖关系和锁机制避免并发任务数据竞争问题 3412 3413 3414 3415# 已知限制 3416 3417## 不支持thread_local变量 3418 3419* Task内部创建或Task间传递的thread_local变量的行为都是不确定的 3420 3421* 原因在于FFRT在编程模型中已经没有thread的概念,只有task的概念 3422* 在C++的语义下,thread_local可以被正常编译,但是使用该thread_local变量的task在哪一个线程上执行时不确定的 3423* 对于使用了FFRT进程中的non-worker,thread_local的行为不受FFRT影响 3424 3425> 类似的,与thread绑定的thread_idx/pthread_specific/递归锁/线程优先级/线程亲和性/递归锁具有相似的问题 3426 3427`建议` 3428 3429* 避免使用这些特性,如必须使用,使用FFRT的task local来替代 3430 3431## 不支持用户在fork出的子进程内使用ffrt 3432 3433## 以动态库方式部署FFRT 3434 3435* 只能以动态库方式部署FFRT,静态库部署可能有多实例问题,例如:当多个被同一进程加载的so都以静态库的方式使用FFRT时,FFRT会被实例化成多份,其行为是未知的,这也不是FFRT设计的初衷 3436 3437## C API中初始化ffrt对象后,对象的置空与销毁由用户负责 3438 3439* 为保证较高的性能,ffrt的C API中内部不包含对对象的销毁状态的标记,用户需要合理地进行资源的释放,重复调用各个对象的destroy操作,其结果是未定义的 3440* 错误示例1,重复调用destroy可能造成不可预知的数据损坏 3441 3442```{.cpp} 3443#include "ffrt.h" 3444void abnormal_case_1() 3445{ 3446 ffrt_task_handle_t h = ffrt_submit_h([](){printf("Test task running...\n");}, NULL, NULL, NULL, NULL, NULL); 3447 ... 3448 ffrt_task_handle_destroy(h); 3449 ffrt_task_handle_destroy(h); // double free 3450} 3451``` 3452 3453* 错误示例2,未调用destroy会造成内存泄漏 3454 3455```{.cpp} 3456#include "ffrt.h" 3457void abnormal_case_2() 3458{ 3459 ffrt_task_handle_t h = ffrt_submit_h([](){printf("Test task running...\n");}, NULL, NULL, NULL, NULL, NULL); 3460 ... 3461 // memory leak 3462} 3463``` 3464 3465* 建议示例,仅调用一次destroy,如有必要可进行置空 3466 3467```{.cpp} 3468#include "ffrt.h" 3469void normal_case() 3470{ 3471 ffrt_task_handle_t h = ffrt_submit_h([](){printf("Test task running...\n");}, NULL, NULL, NULL, NULL, NULL); 3472 ... 3473 ffrt_task_handle_destroy(h); 3474 h = nullptr; // if necessary 3475} 3476``` 3477 3478## 输入输出依赖数量的限制 3479 3480* 使用submit接口进行任务提交时,每个任务的输入依赖和输出依赖的数量之和不能超过8个。 3481* 使用submit_h接口进行任务提交时,每个任务的输入依赖和输出依赖的数量之和不能超过7个。 3482* 参数既作为输入依赖又作为输出依赖的时候,统计依赖数量时只统计一次,如输入依赖是{&x},输出依赖也是{&x},实际依赖的数量是1。