鸿蒙NEXT Function Flow Runtime开发指南:掌握下一代并发编程
面对多核处理器并发编程的复杂性,鸿蒙NEXT的Function Flow Runtime为你提供了一种更简洁高效的解决方案。
在鸿蒙应用开发中,如何高效利用多核处理器、如何管理复杂的异步任务、如何避免线程滥用导致性能下降,一直是开发者面临的挑战。华为鸿蒙NEXT推出的Function Flow Runtime Kit(FFRT) 正是为了解决这些问题而生的并发编程框架。
什么是Function Flow Runtime?
FFRT(Function Flow Runtime)是一种并发编程框架,旨在简化并发编程和任务调度的复杂性。它采用基于任务的调度方式,开发者只需关注任务及其依赖关系,而无需处理底层的线程和计算资源。
FFRT的核心概念
在深入了解FFRT之前,让我们先掌握一些基本概念:
任务(Task):一种面向开发者的编程线索和面向运行时的执行对象,包含一组指令序列及其操作的数据上下文环境。
任务依赖(Task Dependency):任务之间的依赖关系,决定了某个任务是否需要等待其他任务完成才能开始执行。
QoS(Quality of Service):定义了任务的服务质量等级,用于指示任务的优先级和资源分配。
Worker:执行任务的工作线程。每个Worker可以执行多个任务,由调度器负责管理和分配。
调度(Scheduling):决定任务何时以及由哪个Worker执行的过程。
为什么选择FFRT?两种编程模型对比
在传统的线程编程模型中,程序员需要手动创建线程、管理线程生命周期和处理线程同步,这不仅复杂容易出错,还可能导致系统中有大量线程,造成资源浪费。
相比之下,FFRT的任务编程模型具有明显优势:
对比维度 | 线程编程模型 | FFRT任务编程模型 |
---|---|---|
并行度挖掘方式 | 程序员通过创建多线程并把任务分配到每个线程 | 静态分解应用为任务及其依赖,运行时动态调度 |
线程创建责任 | 程序员负责,可能导致线程滥用 | FFRT运行时负责工作线程池管理 |
负载均衡 | 静态映射任务到线程,可能导致负载不均 | 运行时根据线程状态动态调度,均衡负载 |
调度开销 | 内核态调度,开销大 | 用户态协程调度,开销小 |
依赖表达 | 通过线程同步操作增加线程切换 | 显式表达输入输出依赖,运行时管理 |
FFRT采用基于协程的任务执行方式,可以提高任务并行度、提升线程利用率并充分利用多核平台的计算资源,保证系统对所有资源的集约化管理。
FFRT的三大并发范式
在实际业务中,FFRT支持三种不同的并发范式,应对各种复杂场景。
1. 串行队列(Serial Queue)
串行队列确保任务按照提交的顺序依次执行,适用于需要保持特定执行顺序的任务流。
适用场景:
顺序执行任务,避免乱序导致的数据不一致
保证数据安全,避免多线程竞争条件
简化开发,无需手动管理锁和同步机制
2. 并发队列(Concurrent Queue)
并发队列允许多个任务同时执行,提高并发性能,适用于并行计算和高效利用多核处理器。
适用场景:
提高并发度,充分利用多核处理器
资源高效利用,减少任务等待时间
灵活任务调度,支持优先级和QoS
3. 图依赖并发(Task Graph)
图依赖并发范式通过有向图表示任务及其依赖关系,能够清晰地管理和调度复杂依赖关系的任务。
适用场景:
复杂任务依赖关系管理
动态任务调度,根据运行时条件调整
最大化并行执行,提高资源利用率
FFRT实战:构建任务依赖图
FFRT图依赖并发范式支持任务依赖和数据依赖两种方式构建任务依赖图。
任务依赖示例:流媒体视频处理
假设我们有一个视频处理流程,包含以下步骤:视频解析、视频转码、视频缩略图生成、视频水印添加和视频发布。其中转码和缩略图生成可以并行执行。
c
#include <stdio.h> #include "ffrt/ffrt.h"void func_TaskA(void* arg) {printf("视频解析\n"); }void func_TaskB(void* arg) {printf("视频转码\n"); }void func_TaskC(void* arg) {printf("视频生成缩略图\n"); }void func_TaskD(void* arg) {printf("视频添加水印\n"); }void func_TaskE(void* arg) {printf("视频发布\n"); }int main() {// 提交任务Affrt_task_handle_t hTaskA = ffrt_submit_h_f(func_TaskA, NULL, NULL, NULL, NULL);// 提交任务B和C,它们都依赖于任务Affrt_dependence_t taskA_deps[] = {{ffrt_dependence_task, hTaskA}};ffrt_deps_t dTaskA = {1, taskA_deps};ffrt_task_handle_t hTaskB = ffrt_submit_h_f(func_TaskB, NULL, &dTaskA, NULL, NULL);ffrt_task_handle_t hTaskC = ffrt_submit_h_f(func_TaskC, NULL, &dTaskA, NULL, NULL);// 提交任务D,它依赖于任务B和Cffrt_dependence_t taskBC_deps[] = {{ffrt_dependence_task, hTaskB}, {ffrt_dependence_task, hTaskC}};ffrt_deps_t dTaskBC = {2, taskBC_deps};ffrt_task_handle_t hTaskD = ffrt_submit_h_f(func_TaskD, NULL, &dTaskBC, NULL, NULL);// 提交任务E,它依赖于任务Dffrt_dependence_t taskD_deps[] = {{ffrt_dependence_task, hTaskD}};ffrt_deps_t dTaskD = {1, taskD_deps};ffrt_submit_f(func_TaskE, NULL, &dTaskD, NULL, NULL);// 等待所有任务完成ffrt_wait();// 销毁任务句柄ffrt_task_handle_destroy(hTaskA);ffrt_task_handle_destroy(hTaskB);ffrt_task_handle_destroy(hTaskC);ffrt_task_handle_destroy(hTaskD);return 0; }
数据依赖示例:斐波那契数列
数据依赖通过数据的生产者和消费者关系来表达任务依赖。当一个数据对象的签名出现在任务的in_deps
中时,该任务是消费者任务;当出现在out_deps
中时,该任务是生产者任务。
FFRT支持三种数据依赖关系:
Producer-Consumer依赖:生产者任务和消费者任务之间的依赖(Read-after-Write)
Consumer-Producer依赖:消费者任务和下一个版本生产者任务之间的依赖(Write-after-Read)
Producer-Producer依赖:一个版本生产者任务和下一个版本生产者任务之间的依赖(Write-after-Write)
FFRT高级特性
任务伙伴(Job Partner)
从API version 20开始,FFRT支持Job_Partner(任务伙伴)功能,解决多线程协作和动态并发调度问题。
适用场景:
多线程协作:部分功能需要在特定环境运行,其他功能可以在任何环境运行
动态并发调度:根据任务数量动态调整worker数量,提升性能并降低调度开销
任务粒度优化建议
FFRT编程模型中任务的目标颗粒度最小为100us量级。开发者应注意合理控制任务颗粒度:颗粒度过小会增加调度开销,颗粒度过大会降低并行度。
FFRT与云函数集成
鸿蒙NEXT支持端云一体化开发,可以将FFRT与云函数相结合,构建更强大的应用。
创建云函数
在鸿蒙NEXT中创建云函数的步骤:
登录AppGallery Connect,进入云函数主界面
选择"函数"页签,点击"创建函数"
按照"函数配置 → 触发器 → 函数代码 → 层配置"引导顺序配置函数
端侧调用云函数
在端侧代码中调用云函数的示例:
typescript
import cloud from '@hw-agconnect/cloud'@Entry @Component struct MyIndex {@State message:string = ""build() {Column(){Button("调用hello云函数").onClick( async()=>{console.log("s")const result = await cloud.callFunction({name: 'hello', // 云函数名称version: "$latest", // 云函数版本params: {} // 传递参数})this.message = result.getValue().message})Text(this.message)}.width("100%").height("100%")} }
最佳实践与性能优化
1. 合理选择并发范式
根据具体场景选择合适的并发范式:
需要严格顺序执行 → 串行队列
大量独立任务 → 并发队列
复杂依赖关系 → 图依赖并发
2. 避免过度嵌套
虽然FFRT支持复杂的任务依赖关系,但过度嵌套可能导致代码难以维护。建议将复杂逻辑拆分为具名函数或中间变量。
3. 注意任务粒度
任务粒度影响应用执行性能。FFRT任务的目标颗粒度最小为100us量级,合理控制任务粒度可以平衡调度开销和并行度。
4. 利用语法糖提升代码可读性
鸿蒙NEXT的仓颉语言支持尾随Lambda、流操作符等语法糖,可以显著提升FFRT代码的可读性。
尾随Lambda示例:
typescript
// 传统方式 process(function() {println("Hello, HarmonyOS!") })// 尾随Lambda方式 process {println("Hello, HarmonyOS!") }
流操作符示例:
typescript
let numbers = [1, 2, 3, 4] let sum = numbers|> map { it + 1 } // 映射:[2, 3, 4, 5]|> reduce(0) { acc, item => acc + item } // 归约:2+3+4+5=14 println(sum) // 输出:14
总结
Function Flow Runtime Kit是鸿蒙NEXT中强大的并发编程框架,它通过基于任务的编程模型,让开发者能从复杂的线程管理中解脱出来,专注于任务逻辑和依赖关系。无论是简单的顺序任务还是复杂的图依赖任务,FFRT都能提供高效、简洁的解决方案。
通过合理运用FFRT的三种并发范式——串行队列、并发队列和图依赖并发,结合云函数和现代语法特性,开发者可以构建出高性能、高可维护的鸿蒙应用,充分发挥多核处理器的计算能力,为用户提供更流畅的体验。