FFRT的核心并发范式与样例概览

在深入了解代码前,我们先通过一个表格来梳理FFRT支持的三种核心并发范式及其适用场景,这有助于你根据实际需求选择合适的模型。

并发范式 核心特点 适用场景
图依赖并发 通过任务依赖关系构建有向无环图(DAG),运行时自动调度 任务间存在明确的先后顺序或复杂的依赖关系
串行队列 任务按提交顺序依次执行,保证执行顺序 需要避免资源竞争、确保任务顺序执行的场景
并发队列 多个任务同时执行,充分利用多核资源 任务间相互独立,需要提升并发性能的场景

样例一:图依赖并发------流媒体视频处理

这是一个非常经典的任务依赖案例,模拟视频上传后的处理流程:解析 → (转码 & 生成缩略图) → 添加水印 → 发布。其中转码和生成缩略图可以并行执行。

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()
{
    // 提交任务A(视频解析),它没有前置依赖
    ffrt_task_handle_t hTaskA = ffrt_submit_h_f(func_TaskA, NULL, NULL, NULL, NULL, ffrt_auto_destroy_no);
    
    // 提交任务B(视频转码)和任务C(生成缩略图),它们都依赖于任务A
    ffrt_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_auto_destroy_no);
    ffrt_task_handle_t hTaskC = ffrt_submit_h_f(func_TaskC, NULL, &dTaskA, NULL, NULL, ffrt_auto_destroy_no);
    
    // 提交任务D(添加水印),它依赖于任务B和任务C的完成
    ffrt_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, ffrt_auto_destroy_no);
    
    // 提交任务E(视频发布),它依赖于任务D的完成
    ffrt_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;
}

代码解读

  1. 依赖构建 :使用 ffrt_dependence_tffrt_deps_t 结构体来显式定义任务间的依赖关系。例如,任务B和C的依赖列表 dTaskA 中包含了任务A的句柄,这意味着它们必须等A执行完后才开始。

  2. 并行性 :由于任务B和C都只依赖A,且彼此没有依赖,FFRT运行时可以自动让它们并行执行,从而高效利用多核CPU。

  3. 同步等待ffrt_wait() 会阻塞主线程,直到整个任务图中的所有任务都执行完成,确保流程的完整性。

样例二:数据依赖------斐波那契数列计算

这个例子展示了如何用数据依赖 而非任务句柄来表达任务间的关系,非常适合计算有向无环图结构的数值,比如斐波那契数列 F(n) = F(n-1) + F(n-2)

c

复制代码
#include <stdio.h>
#include "ffrt/ffrt.h"

typedef struct {
    int input;
    int* output;
} fib_data_t;

void fib_task(void* arg) {
    fib_data_t* data = (fib_data_t*)arg;
    int n = data->input;
    int* result = data->output;

    if (n <= 1) {
        *result = n; // 递归基
    } else {
        int result1, result2;
        fib_data_t data1 = {n - 1, &result1};
        fib_data_t data2 = {n - 2, &result2};

        // 定义数据依赖:当前任务消耗input数据,产出output数据
        ffrt_dependence_t in_deps[] = {{ffrt_dependence_data, &data->input, ffrt_dep_type_in}};
        ffrt_dependence_t out_deps[] = {{ffrt_dependence_data, data->output, ffrt_dep_type_out}};
        
        ffrt_deps_t deps_in = {1, in_deps};
        ffrt_deps_t deps_out = {1, out_deps};

        // 提交子任务,计算F(n-1)和F(n-2)
        ffrt_submit_f(fib_task, &data1, &deps_in, &deps_out, NULL, ffrt_task_attr_default);
        ffrt_submit_f(fib_task, &data2, &deps_in, &deps_out, NULL, ffrt_task_attr_default);
        
        // 等待两个子任务完成并汇总结果
        ffrt_wait();
        *result = result1 + result2;
    }
}

int main() {
    int n = 10;
    int final_result;
    fib_data_t main_data = {n, &final_result};

    // 计算F(10)
    ffrt_submit_f(fib_task, &main_data, NULL, NULL, NULL, ffrt_task_attr_default);
    ffrt_wait(); // 等待整个计算过程完成

    printf("F(%d) = %d\n", n, final_result);
    return 0;
}

代码解读

  1. 数据签名 :依赖关系不再通过任务句柄,而是通过数据的地址(如 &data->input)来标识。FFRT会跟踪这些数据的生产者和消费者。

  2. 依赖类型

    • ffrt_dep_type_in:表示本任务需要读取该数据。

    • ffrt_dep_type_out:表示本任务会修改或生产该数据。

  3. 自动同步 :运行时根据数据依赖自动确保:当计算 F(n) 时,其依赖的 F(n-1)F(n-2) 必定已经计算完成。这完美体现了 Producer-Consumer 依赖关系。

样例三:串行队列------顺序访问共享资源

当多个任务需要按顺序访问某个共享资源(如一个配置文件、一段内存或一个硬件设备)时,串行队列是最简单可靠的选择。

c

复制代码
#include <stdio.h>
#include "ffrt/ffrt.h"

// 模拟一个共享资源
int shared_counter = 0;

void task_increment(void* arg) {
    // 此任务在串行队列中执行,无需加锁
    shared_counter++;
    printf("Task Increment: shared_counter is now %d\n", shared_counter);
}

void task_double(void* arg) {
    shared_counter *= 2;
    printf("Task Double: shared_counter is now %d\n", shared_counter);
}

int main() {
    // 创建一个串行队列属性
    ffrt_queue_attr_t attr;
    ffrt_queue_attr_init(&attr);
    ffrt_queue_attr_set_type(attr, ffrt_queue_serial);

    // 将任务提交到同一个串行队列中
    for (int i = 0; i < 5; i++) {
        // 任务将按for循环的提交顺序依次执行
        ffrt_submit_f(task_increment, NULL, NULL, NULL, &attr, ffrt_task_attr_default);
        ffrt_submit_f(task_double, NULL, NULL, NULL, &attr, ffrt_task_attr_default);
    }

    ffrt_wait();
    printf("Final value: %d\n", shared_counter);
    
    ffrt_queue_attr_destroy(attr);
    return 0;
}

代码解读

  1. 顺序保证 :通过 ffrt_queue_attr_set_type(attr, ffrt_queue_serial) 设置串行队列,所有提交到此队列的任务都会严格按照先进先出(FIFO) 的顺序执行。

  2. 数据安全 :因为同一时间只有一个任务在执行,所以对 shared_counter 的修改是绝对安全的,无需使用锁,简化了编程模型,避免了死锁风险。

开发注意事项

  1. 任务粒度 :FFRT适合调度任务粒度不小于100微秒的计算任务。任务过小,调度开销占比会变大;任务过大,则无法充分利用系统并行度。

  2. 依赖管理 :务必正确设置任务依赖,否则可能导致数据竞争或任务无法执行。对于不再使用的任务句柄,记得使用 ffrt_task_handle_destroy 进行销毁。

  3. 等待与同步ffrt_wait() 是一个强大的同步原语,它会等待调用者提交的所有任务(包括子孙任务)都完成。在需要协调多个任务结果的场景下非常有用。

希望以上样例能帮助你直观地理解并上手鸿蒙NEXT的FFRT开发。在实际项目中,灵活运用这些并发范式,可以极大地提升应用程序的性能和响应能力。

相关推荐
nashane8 小时前
HarmonyOS 6学习:CapsLock键失效诊断与长截图完整实现指南
学习·华为·harmonyos
richard_yuu10 小时前
鸿蒙心理测评模块实战|PHQ-9/GAD7双量表答题、实时计分与结果本地化存储
华为·harmonyos
不爱吃糖的程序媛13 小时前
2026年Electron 鸿蒙PC环境搭建指南
人工智能·华为·harmonyos
nashane13 小时前
HarmonyOS 6学习:长截图功能开发中的滚动拼接与权限处理实战
人工智能·华为·harmonyos
大师兄666814 小时前
从零开发一个 HarmonyOS 输入法——KikaInputMethod 完整拆解
harmonyos·服务卡片·harmonyos6·formkit
Python私教20 小时前
鸿蒙 NEXT 也能接 MCP?用 ArkTS 跑通 AI Agent 工具链
人工智能·华为·harmonyos
Swift社区1 天前
分布式能力在鸿蒙 PC 上到底怎么用?
分布式·华为·harmonyos
nashane1 天前
HarmonyOS 6学习:外接键盘CapsLock与长截图功能的实战调试与完整解决方案
学习·华为·计算机外设·harmonyos
aqi002 天前
一文理清 HarmonyOS 6.0.2 涵盖的十个升级点
android·华为·harmonyos·鸿蒙·harmony