【鸿蒙实战开发】HarmonyOS-ArkUI中的线程模型

前言

HarmonyOS在演进过程中,先后提出了两种应用模型:FA(Feature Ability)模型和Stage模型。其中,FA模型是早期推出的,支持两种开发范式:类Web范式和声明式范式;而Stage模型作为当前主推的应用模型,仅支持声明式范式。类Web范式与声明式范式最直观的区别在于:前者基于JavaScript语言,所构建的应用中每个页面对应三个文件(.js、.hml和.css);后者基于ArkTS语言(扩展的TypeScript语言),所构建的应用中每个页面对应一个文件(.ets)。
本文不过多关注HarmonyOS中应用模型和开发范式的具体内容,而是聚焦于支撑应用构建用户界面的ArkUI开发框架中的线程模型。后文将对ArkUI线程模型的代码实现以及在不同应用模型和开发范式上的区别进行详细介绍。

1. 线程模型组成

本小节将对ArkUI线程模型的组成结构进行介绍,同时指出不同开发范式以及应用模型在线程模型上的不同之处。
ArkUI框架将任务类型分为如下六种:Platform、JS、UI、GPU、IO和Background。其中,Background类型任务由一个线程池(默认包含八个线程)负责处理,其余类型任务均由单个线程承载。具体的,各类任务对应的线程功能如下:
在这里插入图片描述

需要说明的是,ArkUI中不同应用模型或不同开发范式对应的线程模型是有差异的。区别如下:

●类Web范式:Platform、JS和UI类型的任务会由三个不同的线程执行。
●声明式范式FA模型:Platform类型任务由一个线程执行,JS和UI类型由同一个线程执行。
●声明式范式Stage模型:Platform、JS和UI类型的任务共用同一个线程执行。

另外,目前ArkUI中,IO类型任务会与UI类型任务共用同一个线程执行。且在使能Rosen后端后,GPU类型任务会与UI类型任务也会共用同一个线程执行。后续介绍代码时,会再做说明。

2. 线程模型实现

本小节将对ArkUI线程模型相关代码逐个说明。ArkUI代码仓中,与线程模型相关的主要是下图中的几个文件:

.
├── adapter
│   ├── ohos
│   │   └── entrance
│   │       ├── ace_container.h
│   │       └── ace_container.cpp
│   └── preview
│       └── entrance
│           ├── ace_container.h
│           └── ace_container.cpp
└── frameworks
    ├── base
    │   └── thread
    │       ├── background_task_executor.h
    |       ├── background_task_executor.cpp
    │       ├── cancelable_callback.h
    |       ├── task_executor.h
    |       └── frame_trace_adapter.h
    └── core
        └── common
            └── flutter
                ├── flutter_task_executor.h
                ├── flutter_task_executor.cpp
                ├── flutter_thread_model.h
                └── flutter_thread_model.cpp

在ArkUI框架中,各种类型的任务是通过任务执行器进行管理的,是整个线程模型的核心。

0.任务执行器定义于文件tast_executor.h和flutter_task_executor.h (.cpp) 中,前者定义了任务执行器的基类TaskExecutor,后者定义了任务执行器基于Flutter线程库的实现类FlutterTaskExecutor。
1.在FlutterTaskExecutor中,Background类型的任务会交给后台任务执行器去处理,由文件background_task_executor.h (.cpp) 中的BackgroundTaskExecutor类定义。其他类型的任务由基于flutter线程库创建的五个fml::TaskRunner实例进行处理。
2.文件cancelable_callback.h中,给出了一个基本任务的描述,定义为类CancelableCallback,由其内部类Callback辅助实现。

除此之外,ace_container.h (.cpp) 和flutter_thread_model.h (.cpp) 主要和任务执行器的创建相关。

2.1 任务执行器

任务执行器是整个线程模型的核心,负责将各种类型的任务交给对应的线程或者线程池进行执行。本小节分将对任务执行器的基类TaskExecutor和基于Flutter中线程库的实现类FlutterTaskExecutor进行介绍。

基类TaskExecutor

在TaskExecutor中,主要给出了ArkUI中任务类型的定义以及用于抛同步和异步任务的接口。

如前文所述,ArkUI中的任务类型分为六类,与之呼应,可以看到在tast_executor.h中定义了如下枚举类TaskType:

enum class TaskType : uint32_t {
    PLATFORM = 0,
    UI,
    IO,
    GPU,
    JS,
    BACKGROUND,
    UNKNOWN,
};

在整个ArkUI仓中,将某个任务抛到对应线程执行时,都会以此枚举类型进行标定。

下表对TaskExecutor中提供的所有接口进行简单分类,以说明其作用:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


可以看到,TaskExecutor类中主要是给出了抛同步和异步任务的接口,基本每个接口的形参都有左、右值引用两种形式。在平时开发中,为了提高一丝性能,可以优先选择调用形参为右值的接口,这类接口可以省去一些创建和拷贝实参的性能消耗。具体到代码,可以看到TaskExecutor类提供的接口实现并不复杂,需要特别说明的是:

●异步类型的接口:最终都会调用到虚接口OnPostTask;该接口的实现将在介绍子类FlutterTaskExecutor时进行详细说明;

●同步类型的接口:

○判断是否为Background类型的任务,若是,则直接返回false,即不允许抛Background类型的同步任务。
○调用WillRunOnCurrentThread判断当前任务是否刚好在当前线程执行,如果是,则直接执行该任务。否则调用接口PostTaskAndWait;
○在PostTaskAndWait接口调用了虚接口OnPostTask和CancelableCallback中的WaitUntilComplete方法;后者的实现将在介绍类CancelableCallback时进行详细说明;

上述两点是实现同、异步执行任务的关键接口。

基于Flutter线程库的实现类FlutterTaskExecutor

FlutterTaskExecutor是基于Flutter线程库进行实现的。前文提到的Platform、JS、UI、GPU和IO这五种类型的任务是交由基于flutter线程库创建的五个fml::TaskRunner实例进行处理的。对应FlutterTaskExecutor中的如下属性:

fml::RefPtr<fml::TaskRunner> platformRunner_;
fml::RefPtr<fml::TaskRunner> uiRunner_;
fml::RefPtr<fml::TaskRunner> ioRunner_;
fml::RefPtr<fml::TaskRunner> jsRunner_;
fml::RefPtr<fml::TaskRunner> gpuRunner_;

在OnPostTask方法中,可以看到这五种类型的fml::TaskRunner实例会负责处理对应类型的任务。

// OnPostTask的实现:将各个类型的任务转交给对应的TaskRunner处理
bool FlutterTaskExecutor::OnPostTask(Task&& task, TaskType type, uint32_t delayTime) const
{
    int32_t currentId = Container::CurrentId();
    auto traceIdFunc = [weak = WeakClaim(const_cast<FlutterTaskExecutor*>(this)), type]() {
        auto sp = weak.Upgrade();
        if (sp) {
            sp->taskIdTable_[static_cast<uint32_t>(type)]++;
        }
    };
    TaskExecutor::Task wrappedTask =
        currentId >= 0 ? WrapTaskWithContainer(std::move(task), currentId, std::move(traceIdFunc)) : std::move(task);
​
    switch (type) {
        case TaskType::PLATFORM:
            return PostTaskToTaskRunner(platformRunner_, std::move(wrappedTask), delayTime);
        case TaskType::UI:
            return PostTaskToTaskRunner(uiRunner_, std::move(wrappedTask), delayTime);
        case TaskType::IO:
            return PostTaskToTaskRunner(ioRunner_, std::move(wrappedTask), delayTime);
        case TaskType::GPU:
            return PostTaskToTaskRunner(gpuRunner_, std::move(wrappedTask), delayTime);
        case TaskType::JS:
            return PostTaskToTaskRunner(jsRunner_, std::move(wrappedTask), delayTime);
        case TaskType::BACKGROUND:
            // Ignore delay time
            return BackgroundTaskExecutor::GetInstance().PostTask(std::move(wrappedTask));
        default:
            return false;
    }
}
// PostTaskToTaskRunner的实现
bool PostTaskToTaskRunner(const fml::RefPtr<fml::TaskRunner>& taskRunner, TaskExecutor::Task&& task, uint32_t delayTime)
{
    CHECK_NULL_RETURN_NOLOG(taskRunner, false);
    CHECK_NULL_RETURN_NOLOG(task, false);
​
    if (delayTime > 0) {
        taskRunner->PostDelayedTask(std::move(task), fml::TimeDelta::FromMilliseconds(delayTime));
    } else {
        taskRunner->PostTask(std::move(task));
    }
    return true;
}

其中,比较特殊的是Background类型的任务会被交给后台任务执行器(BackgroundTaskExecutor)的单例对象去处理,具体的实现将在下一小节给出。
此处使用到的五种类型的fml::TaskRunner实例在创建的时候是有所区别的,具体如下。

●platformRunner_的初始化:

platformRunner_是ArkUI框架中的主线程,相较于其他fml::TaskRunner实例比较特殊。
可以看到,platformRunner_是调用接口flutter::PlatformTaskRunner::CurrentTaskRunner(useCurrentEventRunner)返回的,返回值实际上是flutter::PlatformTaskRunnerAdapter::PlatformTaskRunnerAdapter类型的对象。该类通过对EventHandler和EventRunner的封装,继承并实现fml::TaskRunner中与抛任务相关的接口。同时,flutter::PlatformTaskRunner::CurrentTaskRunner(useCurrentEventRunner)的入参为false,实际上是复用的元能力中创建的当前应用的主线程。

void FlutterTaskExecutor::InitPlatformThread(bool useCurrentEventRunner, bool isStageModel)
{
#if defined(OHOS_STANDARD_SYSTEM) || defined(PREVIEW)
    platformRunner_ = flutter::PlatformTaskRunner::CurrentTaskRunner(useCurrentEventRunner);
#else
#if defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
    if (isStageModel) {
        LOGI("using eventhandler as platform thread in stage model.");
        platformRunner_ = flutter::PlatformTaskRunner::CurrentTaskRunner(useCurrentEventRunner);
    } else {
        LOGI("using messageLoop as platform thread in fa model.");
        fml::MessageLoop::EnsureInitializedForCurrentThread();
        platformRunner_ = fml::MessageLoop::GetCurrent().GetTaskRunner();
    }
#else
    fml::MessageLoop::EnsureInitializedForCurrentThread();
    platformRunner_ = fml::MessageLoop::GetCurrent().GetTaskRunner();
#endif
#endif
​
    FillTaskTypeTable(TaskType::PLATFORM);
}

注: flutter::PlatformTaskRunnerAdapter::PlatformTaskRunnerAdapter的实现在三方仓中的flutter中,具体代码目录如下:third_party/flutter/engine/flutter/shell/platform/ohos/platform_task_runner_adapter.h。

uiRunner_、ioRunner_、gpuRunner_的初始化:

这三个实例在是在InitOtherThreads接口中进行初始化,是包含在flutter::TaskRunners形参中传入的。具体如下:

void FlutterTaskExecutor::InitOtherThreads(const flutter::TaskRunners& taskRunners)
{
    uiRunner_ = taskRunners.GetUITaskRunner();
    ioRunner_ = taskRunners.GetIOTaskRunner();
#ifdef FLUTTER_2_5
    gpuRunner_ = taskRunners.GetRasterTaskRunner();
#else
    gpuRunner_ = taskRunners.GetGPUTaskRunner();
#endif
​
    PostTaskToTaskRunner(
        uiRunner_, [] { SetThreadPriority(UI_THREAD_PRIORITY); }, 0);
    PostTaskToTaskRunner(
        gpuRunner_, [] { SetThreadPriority(GPU_THREAD_PRIORITY); }, 0);
​
    PostTaskToTaskRunner(
        uiRunner_, [weak = AceType::WeakClaim(this)] { FillTaskTypeTable(weak, TaskType::UI); }, 0);
    PostTaskToTaskRunner(
        ioRunner_, [weak = AceType::WeakClaim(this)] { FillTaskTypeTable(weak, TaskType::IO); }, 0);
    PostTaskToTaskRunner(
        gpuRunner_, [weak = AceType::WeakClaim(this)] { FillTaskTypeTable(weak, TaskType::GPU); }, 0);
}

调用InitOtherThreads接口传入的flutter::TaskRunners实例是通过FlutterThreadModel类的CreateThreadModel方法创建的:

std::unique_ptr<FlutterThreadModel> FlutterThreadModel::CreateThreadModel(
    bool useCurrentEventRunner, bool hasUiThread, bool hasGpuThread)
{
    // Create threads
    static size_t sCount = 1;
    auto threadLabel = std::to_string(sCount++);
    uint64_t typeMask = 0;
​
    if (hasUiThread) {
        typeMask |= flutter::ThreadHost::Type::UI;
    }
    if (hasGpuThread) {
        typeMask |= flutter::ThreadHost::Type::GPU;
    }
    flutter::ThreadHost threadHost = { threadLabel, typeMask };
​
    // Create Taskrunners
    fml::MessageLoop::EnsureInitializedForCurrentThread();
    fml::RefPtr<fml::TaskRunner> gpuRunner;
    fml::RefPtr<fml::TaskRunner> uiRunner;
    
#if defined(OHOS_PLATFORM) || defined(PREVIEW)
    fml::RefPtr<fml::TaskRunner> platformRunner =
        flutter::PlatformTaskRunnerAdapter::CurrentTaskRunner(useCurrentEventRunner);
#else
#if defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
    fml::RefPtr<fml::TaskRunner> platformRunner;
    if (hasUiThread) {
        platformRunner = fml::MessageLoop::GetCurrent().GetTaskRunner();
    } else {
        LOGI("FlutterThreadModel create platfrom thread by eventhandler.");
        platformRunner = flutter::PlatformTaskRunnerAdapter::CurrentTaskRunner(useCurrentEventRunner);
    }
#else
    fml::RefPtr<fml::TaskRunner> platformRunner;
    platformRunner = fml::MessageLoop::GetCurrent().GetTaskRunner();
#endif
#endif
​
    if (hasUiThread) {
        uiRunner = threadHost.ui_thread->GetTaskRunner();
    } else {
        uiRunner = platformRunner;
    }
    if (hasGpuThread) {
        gpuRunner = threadHost.gpu_thread->GetTaskRunner();
    } else {
        gpuRunner = uiRunner;
    }
​
    flutter::TaskRunners taskRunners(threadLabel, // label
        platformRunner,                           // platform
        gpuRunner,                                // gpu
        uiRunner,                                 // ui
        uiRunner                                  // io
    );
​
    return std::make_unique<FlutterThreadModel>(std::move(threadHost), std::move(taskRunners));
}

可以看到,该函数的第2、3个入参会指定是否启用新的线程执行UI和GPU类型的任务。可以看到:如果不启动新的线程执行UI类型任务,则UI类型任务会共用Platform类型任务的线程;如果不启动新的线程执行GPU类型任务,则GPU类型任务会共用UI类型任务的线程。另外,IO类型的任务会直接共用UI类型任务的线程。这些TaskRunner实例创建好后,会统一交给一个flutter::TaskRunners类的实例进行管理。

jsRunner_的初始化:

jsRunner_和uiRunner_、ioRunner_、gpuRunner_并无本质不同,只是flutter使用的是Dart语言,没有js任务,所以在flutter::TaskRunners中没有包含jsRunner。jsRunner_的初始化由下边的代码给出:

void FlutterTaskExecutor::InitJsThread(bool newThread)
{
    if (newThread) {
        jsThread_ = std::make_unique<fml::Thread>(GenJsThreadName());
        jsRunner_ = jsThread_->GetTaskRunner();
    } else {
        jsRunner_ = uiRunner_;
    }
​
    PostTaskToTaskRunner(
        jsRunner_, [weak = AceType::WeakClaim(this)] { FillTaskTypeTable(weak, TaskType::JS); }, 0);
}

可以看到,在初始化jsRunner_时,会通过入参控制是否另起一个线程。
本小节给出了OnPostTask函数的具体实现,但是,其内部会调用到flutter::TaskRunner中的接口,这涉及到flutter线程库中的实现,基本原理与我们将在下一节介绍的后台任务执行器(BackgroundTaskExecutor)有相通之处。不同之处在于,flutter线程库中对任务队列的处理是基于epoll模型实现的。

2.2 后台任务执行器

可以看到,在上一小节的OnPostTask函数实现中Background类型的任务会被交给

BackgroundTaskExecutor类的单例去处理。在本小节,我们对该类的实现进行介绍。

BackgroundTaskExecutor类实现了一个线程池,该类的实例会持有两个任务队列(对应普通优先级和低优先级)和一组(8个)线程,每个线程的执行体都是一个死循环,在没有任务的时候会处于休眠状态。同时,该类提供了向该线程池抛任务的接口,当抛任务的接口被执行时,对应的任务会被添加到相应的任务队列,同时,会唤醒一个线程去处理该任务。

关键属性

在BackgroundTaskExecutor类中,如下几个关键属性:

std::mutex mutex_;
std::condition_variable condition_;
std::list<Task> tasks_;
std::list<Task> lowPriorityTasks_;
std::list<std::thread> threads_;

●tasks_:普通优先级任务队列
●lowPriorityTasks_:低优先级任务队列
●mutex_:用于保护任务队列的锁
●condition_:用于线程同步,线程池中的线程会调用wait方法阻塞当前线程,直到其他线程抛任务时调用notify_one方法唤醒线程池中的一个线程处理任务。
●threads_:用于保存线程池中的所有线程。

关键方法

在BackgroundTaskExecutor中,我们关注如下三个接口的实现:PostTask、StartNewThreads和ThreadLoop。

StartNewThreads函数

在StartNewThreads函数中,主要关键程序如下,用于启动多个线程,并将ThreadLoop函数设置为每个线程的主函数。

// Start new threads.
std::list<std::thread> newThreads;
for (size_t idx = 0; idx < num; ++idx) {
    newThreads.emplace_back(std::bind(&BackgroundTaskExecutor::ThreadLoop, this, currentThreadNo + idx));
}

●ThreadLoop函数

void BackgroundTaskExecutor::ThreadLoop(uint32_t threadNo)
{
    LOGD("Background thread is started");
​
    SetThreadName(threadNo);
​
    Task task;
    const uint32_t purgeFlag = (1 << (threadNo - 1));
    std::unique_lock<std::mutex> lock(mutex_);
    while (running_) {
        if (tasks_.empty() && lowPriorityTasks_.empty()) {
            if ((purgeFlags_ & purgeFlag) != purgeFlag) {
                condition_.wait(lock);
                continue;
            }
​
            lock.unlock();
            LOGD("Purge malloc cache for background thread %{public}u", threadNo);
            PurgeMallocCache();
            lock.lock();
            purgeFlags_ &= ~purgeFlag;
            continue;
        }
        // deal with tasks_ first. do lowPriorityTasks_ only when all tasks_ done.
        if (!tasks_.empty()) {
            task = std::move(tasks_.front());
            tasks_.pop_front();
        } else {
            task = std::move(lowPriorityTasks_.front());
            lowPriorityTasks_.pop_front();
        }
​
        lock.unlock();
        // Execute the task and clear after execution.
        task();
        task = nullptr;
        lock.lock();
    }
​
    LOGD("Background thread is stopped");
}

可以看到,ThreadLoop函数中主要就是一个死循环。在循环体中,如果普通任务队列或低优先级任务队列不为空,则从任务队列中弹出任务并执行;若两个队列均为空,则通过condition_.wait(lock)阻塞当前线程。

●PostTask函数

此函数针对左值引用和右值引用的任务入参,有两种形式,实现上近乎完全相同,我们只关注其中一个版本即可。具体实现如下:

bool BackgroundTaskExecutor::PostTask(Task&& task, BgTaskPriority priority)
{
    if (!task) {
        return false;
    }
​
    std::lock_guard<std::mutex> lock(mutex_);
    if (!running_) {
        return false;
    }
    FrameTraceAdapter* ft = FrameTraceAdapter::GetInstance();
    if (ft != nullptr && ft->IsEnabled()) {
        switch (priority) {
            case BgTaskPriority::LOW:
                ft->QuickExecute(std::move(task));
                break;
            default:
                ft->SlowExecute(std::move(task));
                break;
        }
        return true;
    }
    switch (priority) {
        case BgTaskPriority::LOW:
            lowPriorityTasks_.emplace_back(std::move(task));
            break;
        default:
            tasks_.emplace_back(std::move(task));
            break;
    }
    condition_.notify_one();
    return true;
}

此函数中,我们不需要关注FrameTraceAdapter相关的内容。可以看出来,函数工作就是将任务加入到对应优先级的队列。并调用condition_.notify_one()唤醒线程池中的一个线程进行处理。

2.3 基本任务类型

CancelableCallback<void(V…)>::Cancel(bool waitUntilCompleted):

●若任务的当前状态为READY,则将状态设置为CANCLED,并不再执行任务,返回true;
●若任务的当前状态为CANCLED,表明任务未执行,且不再执行,返回true;
●若任务的当前状态为RUNNING,等待任务执行完毕,并将任务状态设置为COMPLETED,返回false;
若任务的当前状态为COMPLETED,则表明任务已完成,返回false;

2.4 任务执行器的创建流程

3. fml::TaskRunner

background_task_executor.h

写在最后

有很多小伙伴不知道该从哪里开始学习鸿蒙开发技术?也不知道鸿蒙开发的知识点重点掌握的又有哪些?自学时频繁踩坑,导致浪费大量时间。结果还是一知半解。所以有一份实用的鸿蒙(HarmonyOS NEXT)全栈开发资料用来跟着学习是非常有必要的。

获取完整版高清学习资料,请点击→鸿蒙全栈开发学习资料(安全链接,请放心点击)

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了

最新鸿蒙全栈开发学习线路在这里插入图片描述

鸿蒙HarmonyOS开发教学视频

在这里插入图片描述
在这里插入图片描述

大厂面试真题

在这里插入图片描述

在这里插入图片描述

鸿蒙OpenHarmony源码剖析

在这里插入图片描述

这份资料能帮住各位小伙伴理清自己的学习思路,更加快捷有效的掌握鸿蒙开发的各种知识。有需要的小伙伴自行领取,,先到先得~无套路领取!!

获取这份完整版高清学习资料,请点击→鸿蒙全栈开发学习资料(安全链接,请放心点击)

  • 33
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值