ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

HarmonyOS API 22 NDK多线程实战:并发编程、死锁排查与性能优化

HarmonyOS API 22 NDK多线程实战:并发编程、死锁排查与性能优化 1. 项目背景与核心价值为什么 API 22 的 NDK 多线程值得你立刻跟进先说结论HarmonyOS 6 的 API 22 在 NDK 层的多线程能力上把一批此前只能靠 ArkTS 异步或者奇技淫巧才能实现的并发场景彻底拉回了标准 C/C 的轨道。这意味着如果你手上有视频处理、实时音频渲染、游戏物理引擎、复杂计算密集型任务现在可以直接用std::thread、std::async、pthread甚至 OpenMP 在原生层跑满多核不需要再绕路。做个类比以前在 NDK 里搞多线程像是在别人家的客厅里借厨房做饭——锅碗瓢盆都有但总得小心别碰坏主人的规矩动不动还要回到“客厅等待区”汇报。API 22 之后这间厨房正式划到你名下了灶台给你刀具给你你可以在里面自建多个灶口同时开火只要别放火烧了房子就行。这项能力解决的核心问题有三类。第一类是性能敏感型任务比如视频逐帧处理、图像滤镜叠加这类任务天然适合拆分成多个工作块并行执行第二类是 I/O 密集型任务比如同时读取多个文件、并发网络请求之前你需要在 JS/ArkTS 层做线程池或依赖回调嵌套现在原生层一把梭第三类是实时性要求高的场景比如音频采集与播放、传感器数据流处理这类场景对线程调度和优先级控制要求严格NDK 层直接管理线程才能在毫秒级响应。适合谁来学两类人。一类是从其他平台转过来的 C/C 开发者你们不需要重新学 ArkTS 就能开发 HarmonyOS 应用这篇就是给你铺路的另一类是被 ArkTS 异步写法折磨许久的原生应用开发者你可能写过无数个 Promise 链和 TaskPool 回调现在终于可以在底层发力了。我还得提醒一句多线程能力本身是“老技术”但它在 HarmonyOS 生态里的开放时机、配套 API 形态、限制条件是全新的。这就好比汽车发动机是老技术但装到新平台之后点火顺序、扭矩曲线、电子限速全都不一样了。这篇文章我不会讲太多理论重点放在工程侧怎么落地、踩坑了怎么排让你看完直接能拿去用。2. 新特性机制拆解NDK 多线程创建的底层逻辑与关键变化2.1 从“受限”到“原生”API 22 到底改了什么先回溯一下历史你会更清楚这次变化的份量。早期的 HarmonyOS NDK 并不是完全不支持多线程而是线程的创建、生命周期管理、退出机制都受到运行时约束。典型表现是什么你在 C/C 代码里pthread_create可能能成功但线程优先级调度不一定生效甚至某些系统服务 API 必须回到特定线程才能调用。这种“半开放”状态让很多开发者非常难受——写出来像多线程跑起来像单线程。API 22 对 NDK 多线程的支持我个人理解分三个层面。第一层是“创建自由”标准 C11 及以上的线程创建方式全部放开了std::thread、std::jthread、pthread_create都不再有运行时拦截线程数量理论上可扩展到系统 CPU 核心数甚至更多当然你乱开线程照样会被系统资源限制按在地上摩擦。第二层是“调度自由”我们可以在原生层设置线程优先级、绑定 CPU 核心affinity、设置调度策略这个对实时音视频处理、游戏渲染管线优化意义极大。第三层是“通信自由”线程间同步用的std::mutex、std::condition_variable、std::atomic都是完整可用的互斥量和条件变量的底层实现与 Linux 原生 pthread 一致不再是模拟实现。我个人在实践中感受到的最大变化其实是“写代码时不再心虚”。以前每次创建线程前都要查一遍文档看当前版本是否允许这种用法现在心态变了就当在 Linux 下写普通 C 程序标准库的线程工具直接上手心智负担大幅降低。2.2 从 API 22 编译产物倒看 NDK 配置你其实早具备了这个条件很多朋友看到“NDK 支持多线程”这个特性下意识以为要改工程配置、换工具链版本其实这是个误区。API 22 的能力背后是 NDK r22 及以上版本提供的 libc 标准库只要你的项目满足以下条件就已经具备了使用多线程的基础工程build-profile.json5中ndkVersion设置为较新的版本比如 22.0.xCMake 配置中链接的库包含libc_shared.so或libc_static.a编译时 C 标准至少为 C11推荐 C17能用到更多同步工具。我自己实际跑的项目用的就是 C17 CMake 3.7.1 组合std::jthread配合stop_token做线程安全退出非常顺手。这里顺便提醒一句ndkVersion不是越大越好要跟你用的 HarmonyOS SDK 版本匹配否则编译链不一致很容易出引用符号找不到的问题。2.3 线程优先级与调度策略隐藏的性能密码多线程不只是开几个线程跑任务那么简单。API 22 带来的调度自由让开发者可以控制线程在系统层的运行优先级这一点在实时场景里是刚需。举个例子你在做一个实时音频效果器音频回调线程必须保证低延迟而日志写入线程可以稍微“慢半拍”。如果两个线程优先级一样系统调度器可能让日志线程抢占 CPU 导致音频出现卡顿。API 22 下我们可以用pthread_setschedparam把音频线程设为SCHED_FIFO或较高优先级日志线程保持普通SCHED_OTHER级别。虽然 HarmonyOS 系统仍然有全局调度限制比如普通应用无法抢占系统关键进程但在应用内部的优先级差异化调度已经绰绰有余。线程绑核CPU affinity也是 API 22 下真正可用的能力。我们用pthread_setaffinity_np可以把计算线程绑定到特定大核performance coresI/O 线程绑定到小核efficiency cores。HarmonyOS 的设备通常采用 ARM 的大小核架构合理绑核能避免小核拖累计算任务也能避免大核空转浪费电量。实际项目中我曾经把渲染线程绑到大核、网络线程绑到小核帧率稳定性和耗电表现都有明显改善。2.4 内存安全与生命周期边界多线程的“隐性地雷”自由度越高责任越大。API 22 放开多线程限制后随之而来的就是内存管理问题。这一点很多第一次从单线程切到多线程的开发者都栽过跟头——你在工作线程里访问了一个已经被释放的对象崩溃日志直接指向一个莫名其妙的地址。在 NDK 层做多线程我总结了几个必须遵守的边界纪律。第一跨线程传递的对象必须用共享所有权std::shared_ptr包装绝不能用裸指针传递之后在线程里delete第二回调用到的函数指针或 lambda 捕获列表必须明确捕获对象的生命周期是否覆盖了线程执行周期第三线程退出前要确保没有其他线程还在访问它负责的数据典型的做法是 join 前先让工作线程处理完队列剩余任务并发出“我来不急活着”的信号。另外注意一个 NDK 特有的坑NDK 层创建的线程默认没有 ArkTS 运行时绑定所以这些线程里不能直接调用需要 ArkTS 引擎环境的方法比如访问某些 Java/ArkTS 对象。要回传数据到 UI 线程必须走 NAPI 的线程安全函数机制napi_create_threadsafe_function这个我在后文的实战部分会展开讲。3. 工程配置与基础示例从零搭好 NDK 多线程环境3.1 三个关键配置项详解搭建 NDK 多线程工程第一步是把 CMake 配置文件写对。不多说直接上我验证过的配置cmake_minimum_required(VERSION 3.5.0) project(ndk_thread_demo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(thread_demo SHARED src/thread_demo.cpp src/worker/producer_consumer.cpp ) target_include_directories(thread_demo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) target_link_libraries(thread_demo libace_napi.z.so libc_shared.so )里面有三个关键决策点。第一CMAKE_CXX_STANDARD 17让你能用std::jthread和std::scoped_lock这俩工具在多线程场景里能省掉不少样板代码第二链接libace_napi.z.so是为了后续做 NAPI 回调哪怕当前示例用不到先挂上后面不折腾第三libc_shared.so确保标准库运行时一致避免多线程相关的符号冲突。对应的build-profile.json5里别忘了把ndkVersion指到 API 22 配套的工具链版本并且开启 C 异常支持{ buildOption: { externalNativeOptions: { path: ./CMakeLists.txt, arguments: [ -DCMAKE_CXX_STANDARD17 ], cppFlags: } } }这个配置我建议直接抄实测在 DevEco Studio 6 的配套版本上能一次通过。3.2 看一个最简多线程示例从创建到安全退出环境配好之后我们直接写一个最精简的多线程示例感受一下 API 22 下的创建流程和退出机制。#include thread #include iostream #include chrono void worker(int id) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 注意NDK 层 printf 输出需要关注 fflush printf(worker %d finished\n, id); fflush(stdout); } extern C void start_demo_threads() { std::thread t1(worker, 1); std::thread t2(worker, 2); t1.join(); t2.join(); printf(all workers done\n); fflush(stdout); }这个示例简单到“没什么好说的”但背后有三件事你必须清楚。一是join的必要性——主线程必须等所有工作线程结束否则t1、t2析构时会调用std::terminate进程直接崩溃二是 printf 加fflush的原因——NDK 层 stdout 默认是全缓冲模式不加 fflush 的话日志可能一直憋在缓冲区里让你误以为线程没跑三是extern C的作用——保证函数名不被 C name mangling 改变这样 ArkTS 侧或 dlsym 才能正确找到符号。3.3 线程安全退出std::jthread 的现代解法如果你手头的项目能接受 C20API 22 配套工具链支持我强烈建议直接用std::jthread替代std::thread。区别在哪std::jthread的析构函数会自动请求线程停止并且 join你不需要手动调join更重要的是它原生支持协作式取消——用std::stop_token告诉工作线程该收工了。#include thread #include stop_token #include atomic void cancellable_worker(std::stop_token st) { while (!st.stop_requested()) { // 执行一小块工作 std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } extern C void start_cancellable_demo() { std::jthread worker(cancellable_worker); std::this_thread::sleep_for(std::chrono::seconds(2)); worker.request_stop(); // 请求停止 // 析构时自动 join无需显式等待 }这个能力在 UI 控制的异步任务中尤其好使。比如用户滑走页面ArkTS 侧发起取消请求NDK 层收到信号后可以优雅退出循环、释放资源、回传结果。我以前用std::thread时为“怎么让后台线程停下来”这个问题写了无数版轮询退出逻辑现在一个stop_token全搞定。4. 实战核心NDK 多线程生产者消费者模型完整实现4.1 为什么首选生产者消费者来验证多线程能力聊到多线程绕不开的一个经典模型就是生产者消费者。它看起来简单——一个或几个线程生产数据一个或几个线程消费数据——但它把多线程里的经典问题全集中到一起了线程间数据共享、互斥访问、条件触发有数据可消费了要能及时通知、队列容量控制、线程退出时机。这个模型做顺了你会发现很多真实业务场景都在它的变体里。我先给你一个直观的类比。想象一家奶茶店做奶茶的师傅是生产者顾客是消费者柜台上的空杯子是共享缓冲区。师傅做好一杯就放到柜台上顾客来了就拿走但柜台同时只能放几杯缓冲区容量有限顾客要的品种热点不一致数据内容不同师傅做多了卖不掉是浪费缓冲区溢出做少了一窝蜂来抢消费者饥饿。如何协调生产速度和消费速度、如何保证“一手交钱一手交货”、如何打烊时让师傅收工回家就是多线程同步的全部。这个模型我在实际项目中用到的地方非常多。举两个一个是视频帧处理管线解码线程生产原始帧滤镜线程消费处理帧中间用有界队列缓冲帧率抖动另一个是网络请求批处理下载线程不停把数据塞进队列数据库写入线程批量落盘避免每来一个请求就开一次 I/O。4.2 实现一个有界队列核心同步代码逐行拆解我要实现的队列不是普通的std::queue而是一个有界队列。为什么要有界因为如果生产者太快、消费者太慢无界队列会让内存无限膨胀最后 OOM 崩溃有界队列则天然带背压——生产者发现队列满了就阻塞等待消费者腾出空间这个“阻塞”就是流量控制。直接上代码这是整个示例的灵魂逻辑#include queue #include mutex #include condition_variable #include optional template typename T class BoundedQueue { public: explicit BoundedQueue(size_t capacity) : capacity_(capacity) {} void push(T value) { std::unique_lockstd::mutex lock(mutex_); not_full_.wait(lock, [this]() { return queue_.size() capacity_; }); queue_.push(std::move(value)); not_empty_.notify_one(); } std::optionalT pop() { std::unique_lockstd::mutex lock(mutex_); not_empty_.wait(lock, [this]() { return !queue_.empty() || closed_; }); if (queue_.empty() closed_) { return std::nullopt; } T value std::move(queue_.front()); queue_.pop(); not_full_.notify_one(); return std::move(value); } void close() { std::unique_lockstd::mutex lock(mutex_); closed_ true; not_empty_.notify_all(); } private: std::mutex mutex_; std::condition_variable not_full_; std::condition_variable not_empty_; std::queueT queue_; size_t capacity_; bool closed_ false; };逐段看关键点。push方法里线程会持锁进入条件变量not_full_的等待直到队列长度小于容量才继续执行——也就是说队列满的时候生产者线程会主动让出 CPU而不是无脑自旋空耗。很多新手写生产消费者最常犯的错误就是没有条件变量用sleep轮询结果要么卡死要么耗 CPU 到发烫条件变量的等待机制正是为解决这个问题而生的。pop方法的逻辑要稍微绕一点。等待条件是“队列不为空或者已关闭”这是必须同时兼顾的两个退出条件。这里我用了std::optionalT作为返回值——当消费者发现队列空了且生产者已经下线就返回nullopt表示“没有更多数据了”消费者线程自然退出。这种设计比返回一个空对象或直接抛异常更优雅。close方法是整个退出的总开关。它设置closed_ true后调用notify_all把所有等待中的消费者唤醒这样消费者不会因为一个空队列无限阻塞下去。这个“主动关停”的动作容易被忽略但它是让多线程程序不悬挂、不泄漏的关键。4.3 组装完整的生产者消费者示例代码可直接抄队列实现好了接下来组装整个线程池逻辑。我设计一个简化的场景2 个生产线程、2 个消费线程生产者每 10 毫秒产出 5 个整数消费者收到后直接打出日志。#include bounded_queue.h #include thread #include cstdio #include atomic static BoundedQueueint g_queue(10); // 容量为10的有界队列 static std::atomicbool g_producers_done{false}; static std::atomicint g_consumed_count{0}; void producer(int id) { for (int i 0; i 5; i) { int value id * 100 i; g_queue.push(value); printf([Producer %d] push %d\n, id, value); fflush(stdout); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } void consumer(int id) { while (true) { auto value g_queue.pop(); if (!value.has_value()) { break; // 队列关闭且为空退出 } g_consumed_count; printf([Consumer %d] got %d\n, id, *value); fflush(stdout); } } extern C void run_producer_consumer() { g_producers_done false; g_consumed_count 0; // 先创建消费者确保不会漏掉生产者数据 std::thread consumer1(consumer, 1); std::thread consumer2(consumer, 2); std::thread producer1(producer, 1); std::thread producer2(producer, 2); producer1.join(); producer2.join(); // 只有所有生产者都下线了才能关闭队列 g_queue.close(); consumer1.join(); consumer2.join(); printf(All done, consumed count %d\n, g_consumed_count.load()); fflush(stdout); }这段代码里有个细节值得单独拿出来说为什么先把消费者线程创建出来再创建生产者线程因为如果生产者先启动它可能会把队列塞满然后阻塞在push上而消费者还没创建就无法腾出空间直接死锁。先启动消费者的意义是让消费者提前进入等待保证生产者的 push 永远不至于阻塞无后援。这个顺序是“拉高成功率”的关键经验之一。整个执行流程无论你怎么切最终语义是10 个产品全部被消费consumed_count一定是 10消费者线程全部优雅退出不悬挂、不崩溃。你可以反复跑结果都是稳定的。4.4 NAPI 回调NDK 多线程如何与 UI 层沟通终于到了 NDK 多线程项目绕不开的“最后一公里”工作线程计算结果怎么通知 ArkTS 侧刷新界面先讲一个反面案例。我在早期项目里写过这样的代码工作线程里直接调用一个全局的 NAPI 函数指针去回调 JS。结果是什么应用直接崩溃日志指向napi_call_function里的非法访问错误。原因很简单NDK 创建的工作线程没有 ArkTS 运行时栈你不能在这些线程里随便调用 NAPI 函数。正确姿势是用 NAPI 的线程安全函数机制。API 22 下napi_create_threadsafe_function、napi_call_threadsafe_function两个接口是官方推荐的跨线程通信方式。思路是这样的在模块初始化阶段主线程环境下用napi_create_threadsafe_function创建一个安全函数并指定最终在 JS 主线程或用户指定线程执行的回调然后把这个安全函数指针传给工作线程工作线程在任意时刻调用napi_call_threadsafe_function把计算结果包在napi_value里传过去NAPI 运行时自动把回调调度到正确线程执行你不需要关心底层队列和同步。用代码表示核心调用napi_value napi_run_async_task(napi_env env, napi_callback_info info) { napi_threadsafe_function tsfunc; napi_create_threadsafe_function( env, /* 回调函数 */ js_callback, /* async resource */ nullptr, /* resource name */ nullptr, /* max_queue_size */ 0, // 不限队列深度 /* initial_thread_count */ 1, // 初始线程计数 /* thread_finalize_data / func */ nullptr, /* context */ nullptr, /* call_js_cb */ CallJsCallback, tsfunc ); std::thread worker([tsfunc]() { int result heavy_compute(); // 在线程里跑大任务 // 把结果传给 JS 侧 napi_call_threadsafe_function(tsfunc, result, napi_tsfn_blocking); napi_release_threadsafe_function(tsfunc, napi_tsfn_release); }); worker.detach(); return GetUndefined(env); }这里需要留意的是napi_release_threadsafe_function的调用时机。它的作用是让 NAPI 明确知道“初始线程”已经用完标志线程安全函数可以被清理。如果你忘记调用回调和相关资源会一直挂在内存里干脆点说就是泄漏。每次调用napi_call_threadsafe_function时也要保证传入的线程安全函数指针是有效的这需要你设计好线程间的所有权传递。5. 多线程实战中的典型坑与排查方法实录5.1 死锁排查一个你可能永远注意不到的死锁场景多线程写多了死锁是最令人头疼的问题。找死锁没别的办法核心还是定位“某线程持有锁不释放另一线程又去请求这把锁”。我强烈建议你提前在代码里加锁的命名规范否则调试时根本分不清哪把锁。举一个我真实踩过的死锁案例。当时我写了一个缓存模块线程 A 持有缓存锁后去请求一个外部服务线程 B 持有服务锁后却在等缓存锁释放。看起来毫无关联的两把锁在特定竞争条件下形成了闭环。排查时我先抓了allthread的栈信息发现线程 A 阻塞在服务请求上线程 B 阻塞在缓存锁上而服务锁又被线程 A 占用缓存锁被线程 B 占用——一个典型的 ABBA 死锁。解决办法有两层。第一层是规避绝不在持有一把业务锁的情况下去请求另一把锁如果实在避免不了就规定所有线程统一按照同一顺序加锁比如先缓存锁后服务锁打破循环等待。第二层是兜底API 22 支持std::scoped_lock可以同时锁多把互斥量用它来避免死锁反而是更经济的选择。当然最理想的还是尽量用无锁数据结构或原子变量替代锁但那个工程成本更高适合真正高频的路径。5.2 数据竞争定位AddressSanitizer 和 Log 双管齐下数据竞争data race是比死锁更隐蔽的问题。死锁至少会卡崩让你看到数据竞争可能只是偶尔在特定机器上输出一个错乱的数字或者周期性地让你在生产环境跑几周才出现一次崩溃。我用过的排查工具组合是 ASanAddressSanitizer加条件日志。API 22 的 NDK 工具链自带 ASan 支持需要在 CMake 里打开编译选项target_compile_options(thread_demo PUBLIC -fsanitizeaddress) set_target_properties(thread_demo PROPERTIES LINK_FLAGS -fsanitizeaddress )注意 APK 里必须明显包一层wrap.sh并在application节点配置android:debuggabletrue这样才能在真机上把 ASan 的运行时日志打出来。没有 Wrap 脚本ASan 根本跑不起来。但 ASan 不是万能的——它对堆越界访问和 use-after-free 的检测很敏锐对纯数据竞争却可能漏报。此时你需要辅以 log 定位。技巧是在每个访问共享数据的关键路径前后打印带线程 ID 的日志比如 format 成[Thread-X] write cache keyfoo然后用脚本统计同一 key 是否有交错的读写时间窗。数据竞争出现的特征非常明显同一变量在不同线程中访问的 LLVM 事件被交错打出你就可以顺着时间窗反推哪两个线程最可疑。5.3 CPU 240% 排查你真的需要开这么多线程吗多线程项目上线后最常见的性能事故之一是 CPU 占用率异常飙升。很多开发者反映“我明明就开了 4 个线程怎么系统工具显示 240% 的占用率”首先普及一个概念CPU 百分比是相对单核而言的240% 实际上意味着平均占用了大约 2.4 个核。如果你的设备是 8 核这个占用可能还说得过去但对比你自己预期的“轻量任务”就明显不对了。排查方向有三个。第一个是线程数是否正确。我曾见过有人把日志推送逻辑误写成“每个消息就 spawn 一个线程”高峰期一次性开了几十个线程每个都在做阻塞 I/O自然 CPU 炸了。正确的做法是用固定线程池复用工作线程做任务调度而不是无脑创建。第二个是锁竞争导致的上下文切换暴增——大量线程同时争抢一把锁系统调度器忙于切换线程状态CPU 占比虚高实际业务吞吐量却很低。第三个是无界队列导致消费者长期空转——队列造数据造得太快消费者线程全在跑但没有实际推进业务。我建议任何 NDK 多线程项目启动后立刻用系统自带的资源监控工具观察线程数和 CPU 占用变化基线。如果线程数在空闲状态还异常增加多线程设计大概率出了问题。API 22 提供的std::thread能开得像杂草一样疯长但真正的工程能力在于“开得多但用得好”。5.4 日志全缓冲导致误判“粘住”这个坑非常不起眼但几乎人人都撞过。NDK 层printf和std::cout默认是全缓冲模式多条日志会攒在缓冲区里直到装满或程序退出才一起输出。这会让你的调试体验变得诡异——明明某条日志打印的位置在代码里靠前屏幕上却先显示后面的内容或者干脆什么都没显示。解决方法是每个关键日志后加fflush(stdout)或者直接用fprintf(stderr, ...)——stderr 默认是不缓冲的。更好的方案是用 HarmonyOS 自带的日志接口OH_LOG_Print它走系统日志器天然避免缓冲问题而且带时间戳和线程标签多线程排查时会顺手很多。这里强烈建议正式项目的 NDK 代码里尽量少用 printf 裸输出统一走OH_LOG宏封装不然你调试时会被日志时序问题折磨到怀疑人生。5.5 线程创建的“起跑线”规则核心线程数如何定最后一个高频疑问到底应该创建多少个线程这个没有标准答案但有一个经验公式——如果你是 I/O 密集型线程池核心线程数可以设为 CPU 核心数的 2 到 4 倍因为大部分时间线程都在等待 I/O 返回如果你是计算密集型核心线程数 ≈ CPU 核心数 1。为什么多这么 1 个因为线程偶尔会被调度打断进入等待状态多出一个线程可以补位。API 22 下获取 CPU 核心数也很简单unsigned int cores std::thread::hardware_concurrency();但注意这个值可能包含小核实际“大核可并行数”需要你在运行时根据调频器或厂商设备属性读出。规格数据写死虽然简单但抗不了设备差异。我做的视频处理项目就是根据hardware_concurrency动态计算线程池大小再针对不同机型做上限裁剪整体适应性比之前写死 4 线程好得多。6. 进阶方向API 22 多线程能力的未来扩展空间多线程能力在 NDK 层全面放开之后我在想的一件事是下一步能把这些能力组合成什么更高级的工程设施目前 API 22 提供的还只是“原料级能力”——线程创建、同步原语、原子操作你还需要自己组装线程池、任务队列、异步模型。但我认为这不代表你会永远停留在手工保卫层的阶段最近我就在尝试把以下三个方向搭起来。第一个是通用线程池封装。既然系统允许原生层开线程那么一个引用计数安全的线程池就应该封装成公共库内部维护任务队列、热线程扩容、熔断退避外界只需提交std::functionvoid()任务。这类封装在公司多项目复用意义非常大能让你专注于业务而不是反复写 join 逻辑。第二个是协程与线程的混用。很多 C 项目已经在用协程做异步API 22 放开线程能力后协程调度器可以把阻塞型任务丢给工作线程非阻塞型任务留在协程里跑这种混编模式在视频解码和网络框架里都有广阔的应用空间。我本地已经在用小实验跑通了一个简化版调度器目前效果不错。第三个是结合 HarmonyOS 分布式能力的跨设备线程调度。这个稍微远一点但思路是——NDK 层的多线程能力已经不局限于单设备那么是否可以把计算任务的一部分线程调度到同一鸿蒙生态下的其他设备上当然这依赖于远超 API 22 能力的后续运行时支持现阶段只是我个人的一个方向。所以我最后想说的是API 22 的 NDK 多线程不是终点它更像是给 C/C 开发者在鸿蒙生态里递过来的一把钥匙。你熟悉的标准线程工具终于能原汁原味地跑起来接下来能做什么完全取决于你把组合的想象力发挥到哪一步。我从以前“绕路走”的心态切换到现在的“直接写”这种感受值得每个人亲身体验一次。
返回列表