鸿蒙多线程开发——线程间数据通信对象03(sendable)

1、简 介

在传统JS引擎上,对象的并发通信开销的优化方式只有一种,就是把实现下沉到Native侧,通过Transferable对象的转移或共享方式降低并发通信开销。而开发者仍然还有大量对象并发通信的诉求,这个问题在业界的JS引擎实现上并没有得到解决。

ArkTS提供了Sendable对象类型,在并发通信时支持通过引用传递来解决上述问题。

Sendable对象为可共享的,其跨线程前后指向同一个JS对象,如果其包含了JS或者Native内容,均可以直接共享,如果底层是Native实现的,则需要考虑线程安全性。通信过程如下图所示:

图片

与其它ArkTS对象不一样的是,符合Sendable协议的数据对象在运行时必须是类型固定的对象。

当多个并发实例尝试同时更新Sendable数据时,会发生数据竞争。因此,ArkTS提供了异步锁的机制来避免不同并发实例间的数据竞争。同时,还可以通过对象冻结接口冻结对象,将其变为只读对象,就可以不用考虑数据的竞争问题。

Sendable对象提供了并发实例间高效的通信效率,即引用传递的能力,一般适用于开发者自定义大对象需要线程间通信的场景,例如子线程读取数据库的数据返回主线程。

2、Sendable对象类型基础概念

2.1、Sendable协议

Sendable协议定义了ArkTS的可共享对象体系及其规格约束。符合Sendable协议的数据(以下简称Sendable对象)可以在ArkTS并发实例间传递。

默认情况下,Sendable数据在ArkTS并发实例间(包括主线程、TaskPool、Worker线程)传递的行为是引用传递。同时,ArkTS也支持Sendable数据在ArkTS并发实例间拷贝传递。

2.2、ISendable接口

在ArkTS语言基础库@arkts.lang中引入了interface ISendable,没有任何必须的方法或属性。ISendable是所有Sendable类型(除了null和undefined)的父类型。ISendable主要用在开发者自定义Sendable数据结构的场景中。类装饰器@Sendable装饰器是implement ISendable的语法糖。

2.3、@Sendable装饰器

用于声明并校验Sendable类以及Sendable函数。有以下需要注意的使用限制:

  • Sendable class只能继承Sendable class,普通Class不可以继承Sendable class。

  • 装饰的对象内的属性类型限制

    1. 支持string、number、boolean、bigint、null、undefined、Sendable class、collections.Array、collections.Map、collections.Set、ArkTSUtils.locks.AsyncLock。

    2. 禁止使用闭包变量。

    3. 不支持通过#定义私有属性,需用private。

    4. 不支持计算属性。

  • 成员属性必须显式初始化。成员属性不能跟感叹号。

  • 允许使用local变量、入参和通过import引入的变量。禁止使用闭包变量,定义在顶层的Sendable class和Sendable function除外。

  • 不支持增加属性、不支持删除属性、允许修改属性,修改前后属性的类型必须一致、不支持修改方法。

Sendable类使用示例如下:

@Sendableclass SendableTestClass {  desc: string = "sendable: this is SendableTestClass ";  num: number = 5;  printName() {    console.info("sendable: SendableTestClass desc is: " + this.desc);  }  get getNum(): number {    return this.num;  }}

Sendable函数使用示例如下:

@Sendabletype SendableFuncType = () => void;@Sendableclass TopLevelSendableClass {  num: number = 1;  PrintNum() {    console.info("Top level sendable class");  }}@Sendablefunction TopLevelSendableFunction() {  console.info("Top level sendable function");}@Sendablefunction SendableTestFunction() {  const topClass = new TopLevelSendableClass(); // 顶层sendable class  topClass.PrintNum();  TopLevelSendableFunction(); // 顶层sendable function  console.info("Sendable test function");}@Sendableclass SendableTestClass {  constructor(func: SendableFuncType) {    this.callback = func;  }  callback: SendableFuncType; // 顶层sendable function  CallSendableFunc() {    SendableTestFunction(); // 顶层sendable function  }}let sendableClass = new SendableTestClass(SendableTestFunction);sendableClass.callback();sendableClass.CallSendableFunc();

2.4、Sendable支持的数据类型

  • 所有的ArkTS基本数据类型:boolean, number, string, bigint, null, undefined。

  • ArkTS语言标准库中定义的容器类型数据(须显式引入@arkts.collections)。

  • ArkTS语言标准库中定义的异步锁对象(须显式引入@arkts.utils)。

  • 继承了ISendable的interface。

  • 标注了@Sendable装饰器的class。

  • 标注了@Sendable装饰器的function。

  • 接入Sendable的系统对象。

    • 共享用户首选项

    • 可共享的色彩管理

    • 基于Sendable对象的图片处理

    • 资源管理

    • SendableContext对象管理

  • 元素均为Sendable类型的union type数据。

  • JS内置对象在并发实例间的传递遵循结构化克隆算法,跨线程行为是拷贝传递。因此JS内置对象的实例不是Sendable类型。

  • 对象字面量、数组字面量在并发实例间的传递遵循结构化克隆算法,跨线程行为是拷贝传递。因此,对象字面量和数组字面量不是Sendable类型。

2.5、Sendable的实现原理

为了实现Sendable数据在不同并发实例间的引用传递,Sendable共享对象会分配在共享堆中,以实现跨并发实例的内存共享。

共享堆(SharedHeap)是进程级别的堆空间,与虚拟机本地堆(LocalHeap)不同的是,LocalHeap只能被单个并发实例访问,而SharedHeap可以被所有线程访问。一个Sendable共享对象的跨线程行为是引用传递。因此,Sendable可能被多个并发实例引用,判断Sendable共享对象是否存活,取决于所有并发实例的对象是否存在对此Sendable共享对象的引用。

SharedHeap与LocalHeap关系图

图片

各个并发实例间的LocalHeap是隔离的,SharedHeap是进程级别的堆,可以被所有的并发实例引用。但是SharedHeap不能引用LocalHeap中的对象。

3、Sendable使用场景

Sendable对象可以在不同并发实例间通过引用传递。通过引用传递方式传输对象相比序列化方式更加高效,同时不会丢失class上携带的成员方法。因此,Sendable主要可以解决两个场景的问题:

  • 跨并发实例传输大数据(例如可能达到100KB以上的数据)。

  • 跨并发实例传递带方法的class实例对象。

3.1、跨并发实例传输大数据场景

由于跨并发实例序列化的开销随着数据量线性增长,因此当传输数据量较大时(100KB数据大约1ms传输耗时),跨并发实例的拷贝开销大,影响应用性能。引用传递方式传输对象可提升性能。示例如下:

// Index.etsimport { taskpool } from '@kit.ArkTS';import { testTypeA, testTypeB, Test } from './sendable';import { BusinessError, emitter } from '@kit.BasicServicesKit'; // 在并发函数中模拟数据处理@Concurrentasync function taskFunc(obj: Test) {  console.info("test task res1 is: " + obj.data1.name + " res2 is: " + obj.data2.name);}async function test() {  // 使用taskpool传递数据  let a: testTypeA = new testTypeA("testTypeA");  let b: testTypeB = new testTypeB("testTypeB");  let obj: Test = new Test(a, b);  let task: taskpool.Task = new taskpool.Task(taskFunc, obj);  await taskpool.execute(task);} @Concurrentfunction SensorListener() {  // 监听逻辑  // ...}@Entry@Componentstruct Index {  build() {    Column() {      Text("Listener task")        .id('HelloWorld')        .fontSize(50)        .fontWeight(FontWeight.Bold)        .onClick(() => {          let sensorTask = new taskpool.LongTask(SensorListener);          emitter.on({ eventId: 0 }, (data) => {            // Do something here            console.info(`Receive ACCELEROMETER data: {${data.data?.x}, ${data.data?.y}, ${data.data?.z}`);          });          taskpool.execute(sensorTask).then(() => {            console.info("Add listener of ACCELEROMETER success");          }).catch((e: BusinessError) => {            // Process error          })        })      Text("Data processing task")        .id('HelloWorld')        .fontSize(50)        .fontWeight(FontWeight.Bold)        .onClick(() => {          test();        })    }    .height('100%')    .width('100%')  }}// sendable.ets// 将数据量较大的数据在Sendable class中组装@Sendableexport class testTypeA {  name: string = "A";  constructor(name: string) {    this.name = name;  }}@Sendableexport class testTypeB {  name: string = "B";  constructor(name: string) {    this.name = name;  }}@Sendableexport class Test {  data1: testTypeA;  data2: testTypeB;  constructor(arg1: testTypeA, arg2: testTypeB) {    this.data1 = arg1;    this.data2 = arg2;  }}

3.2、跨并发实例传递带方法的class实例对象

由于序列化传输实例对象时会丢失方法,在必须调用实例方法的场景中,需使用引用传递方式进行开发。在数据处理过程中有需要解析的数据,可使用ASON工具进行数据解析。示例如下:

// Index.etsimport { taskpool, ArkTSUtils } from '@kit.ArkTS';import { SendableTestClass, ISendable } from './sendable'; // 在并发函数中模拟数据处理@Concurrentasync function taskFunc(sendableObj: SendableTestClass) {  console.info("SendableTestClass: name is: " + sendableObj.printName() + ", age is: " + sendableObj.printAge() + ", sex is: " + sendableObj.printSex());  sendableObj.setAge(28);  console.info("SendableTestClass: age is: " + sendableObj.printAge());   // 解析sendableObj.arr数据生成JSON字符串  let str = ArkTSUtils.ASON.stringify(sendableObj.arr);  console.info("SendableTestClass: str is: " + str);   // 解析该数据并生成ISendable数据  let jsonStr = '{"name": "Alexa", "age": 23, "sex": "female"}';  let obj = ArkTSUtils.ASON.parse(jsonStr) as ISendable;  console.info("SendableTestClass: type is: " + typeof obj);  console.info("SendableTestClass: name is: " + (obj as object)?.["name"]); // 输出: 'Alexa'  console.info("SendableTestClass: age is: " + (obj as object)?.["age"]); // 输出: 23  console.info("SendableTestClass: sex is: " + (obj as object)?.["sex"]); // 输出: 'female'}async function test() {  // 使用taskpool传递数据  let obj: SendableTestClass = new SendableTestClass();  let task: taskpool.Task = new taskpool.Task(taskFunc, obj);  await taskpool.execute(task);} @Entry@Componentstruct Index {  @State message: string = 'Hello World';  build() {    RelativeContainer() {      Text(this.message)        .id('HelloWorld')        .fontSize(50)        .fontWeight(FontWeight.Bold)        .alignRules({          center: { anchor: '__container__', align: VerticalAlign.Center },          middle: { anchor: '__container__', align: HorizontalAlign.Center }        })        .onClick(() => {          test();        })    }    .height('100%')    .width('100%')  }}​​​​​// sendable.ets// 定义模拟类Test,模仿开发过程中需传递带方法的classimport { lang, collections } from '@kit.ArkTS'export type ISendable = lang.ISendable;@Sendableexport class SendableTestClass {  name: string = 'John';  age: number = 20;  sex: string = "man";  arr: collections.Array<number> = new collections.Array<number>(1, 2, 3);  constructor() {  }  setAge(age: number) : void {    this.age = age;  }  printName(): string {    return this.name;  }  printAge(): number {    return this.age;  }  printSex(): string {    return this.sex;  }}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/61820.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenHarmony-3.驱动HDF

OpenHarmony HDF 框架 1.OpenHarmony HDF 框架概述 OpenHarmony驱动子系统采用C面向对象编程模型构建&#xff0c;通过平台解耦、内核解耦&#xff0c;兼容不同内核&#xff0c;提供了归一化的驱动平台底座&#xff0c;旨在为开发者提供更精准、更高效的开发环境&#xff0c;力…

ESP8266 STA模式TCP客户端 电脑手机网络调试助手

1.STA模式TCP客户端和电脑网络调试助手 2.STA模式TCP客户端和手机网络调试助手

【JavaEE进阶】 JavaScript

本节⽬标 了解什么是JavaScript, 学习JavaScript的常⻅操作, 以及使⽤JQuery完成简单的⻚⾯元素操作. 一. 初识 JavaScript 1.JavaScript 是什么 JavaScript (简称 JS), 是⼀个脚本语⾔, 解释型或即时编译型的编程语⾔. 虽然它是作为开发Web⻚⾯的脚本语⾔⽽出名&#xff0c;…

神经网络(系统性学习一):入门篇——简介、发展历程、应用领域、基本概念、超参数调优、网络类型分类

相关文章&#xff1a; 神经网络中常用的激活函数 神经网络简介 神经网络&#xff08;Neural Networks&#xff09;是受生物神经系统启发而设计的数学模型&#xff0c;用于模拟人类大脑处理信息的方式。它由大量的节点&#xff08;或称为“神经元”&#xff09;组成&#xff0…

缓冲区的奥秘:解析数据交错的魔法

目录 一、理解缓存区的好处 &#xff08;一&#xff09;直观性的理解 &#xff08;二&#xff09;缓存区的好处 二、经典案例分析体会 &#xff08;一&#xff09;文件读写流&#xff08;File I/O Buffering&#xff09; BufferedOutputStream 和 BufferedWriter 可以加快…

SpringBoot 集成 html2Pdf

一、概述&#xff1a; 1. springboot如何生成pdf&#xff0c;接口可以预览可以下载 2. vue下载通过bold如何下载 3. 一些细节&#xff1a;页脚、页眉、水印、每一页得样式添加 二、直接上代码【主要是一个记录下次开发更快】 模板位置 1. 导入pom包 <dependency><g…

MySQL Join 的原理与优化实践

文章目录 引言一、基础准备&#xff1a;创建环境与示例数据1. 初始化示例表2. 示例 Join 查询3. EXPLAIN 输出分析 二、MySQL Join 的核心算法与执行机制1. 三种 Join 算法的实现与原理1.1 Index Nested-Loop Join&#xff08;INLJ&#xff09;1.2 Simple Nested-Loop Join&…

阿里Qwen系列开源模型介绍

模型种类丰富 Qwen2&#xff1a;包含Qwen2-0.5b、Qwen2-1.5b、Qwen2-7b、Qwen2-57b-a14b以及Qwen2-72b等五种规模的预训练和指令微调模型&#xff0c;其在多语言处理、长文本处理、代码生成、数学和逻辑推理等能力上&#xff0c;在mmlu、gpqa、humaneval等国际测评中得到了验证…

Redis设计与实现 学习笔记 第二十章 Lua脚本

Redis从2.6版本引入对Lua脚本的支持&#xff0c;通过在服务器中嵌入Lua环境&#xff0c;Redis客户端可以使用Lua脚本&#xff0c;直接在服务器端原子地执行多个Redis命令。 其中EVAL命令可以直接对输入的脚本进行求值&#xff1a; 而使用EVALSHA命令则可以根据脚本的SHA1校验…

DevOps 之 CI/CD入门操作 (二)

CI/CD简介 基于Jenkins拉取GitLab的SpringBoot代码进行构建发布到测试环境实现持续集成 基于Jenkins拉取GitLab指定发行版本的SpringBoot代码进行构建发布到生产环境实现CD实现持续部署 一、CI流程 1.1 新建项目 新建一个简单的springboot项目 写一个简单的Controller 运行测…

C++ STL - vector/list讲解及迭代器失效

vector 使用 vector 是一个动态数组. 构造/拷贝构造/赋值重载函数 int main() {// 是一个模板, 在实例化的时候, 需要指明类型std::vector<int> first; // 一个空的数组std::vector<int> second (4,100); // 设置初始空间大小为 4 个int, 全部初始化为 100std::v…

AWS 新加坡EC2 VPS 性能、线路评测及免费注意事项

原文论坛给你更好的阅读讨论体验&#x1f490;&#xff1a; AWS 新加坡EC2 VPS 性能、线路评测及免费注意事项 - VPS - 波波论坛 引言 对于那些习惯薅“羊毛”的朋友来说&#xff0c; AWS 的 免费套餐 可能已经非常熟悉。这台vps是我用外币卡薅的免费的12个月的机器&#xf…

TritonServer中加载模型,并在Gunicorn上启动Web服务调用模型

TritonServer中加载模型,并在Gunicorn上启动Web服务调用模型 一、TritonServer中加载模型1.1 搭建本地仓库1.2 配置文件1.3 服务端代码1.4 启动TritonServer二、Gunicorn上启动Web服务2.1 安装和配置Gunicorn2.2 启动Gunicorn三、调用模型四、性能优化与监控五、总结在深度学习…

容器安全检测和渗透测试工具

《Java代码审计》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484219&idx1&sn73564e316a4c9794019f15dd6b3ba9f6&chksmc0e47a67f793f371e9f6a4fbc06e7929cb1480b7320fae34c32563307df3a28aca49d1a4addd&scene21#wechat_redirect Docker-bench-…

使用ENSP实现NAT

一、项目拓扑 二、项目实现 1.路由器AR1配置 进入系统试图 sys将路由器命名为R1 sysname R1关闭信息中心 undo info-center enable进入g0/0/0接口 int g0/0/0将g0/0/0接口IP地址配置为12.12.12.1/30 ip address 12.12.12.1 30进入e0/0/1接口 int g0/0/1将g0/0/1接口IP地址配置…

pnpm:包管理的新星,平替 npm 和 yarn

​ pnpm&#xff0c;一个老牌的 node.js 包管理器&#xff0c;支持 npm 的所有功能&#xff0c;完全足以用来替代 npm。它采用全局存储&#xff0c;每个项目内部使用了硬链接&#xff0c;所以很省空间&#xff0c;安装速度快。 本文介绍下 pnpm 的基本概念&#xff0c;安装、…

【大数据学习 | Spark-Core】Spark的分区器(HashPartitioner和RangePartitioner)

之前学过的kv类型上面的算子 groupby groupByKey reduceBykey sortBy sortByKey join[cogroup left inner right] shuffle的 mapValues keys values flatMapValues 普通算子&#xff0c;管道形式的算子 shuffle的过程是因为数据产生了打乱重分&#xff0c;分组、排序、join等…

计算机网络基础全攻略:探秘网络构建块(1/10)

一、计算机网络基础概念 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备&#xff0c;通过通信线路和通信设备连接起来&#xff0c;在网络操作系统&#xff0c;网络管理软件及网络通信协议的管理和协调下&#xff0c;实现资源共享和信息传递的计算机系统…

游戏陪玩系统开发功能需求分析

电竞游戏陪玩系统是一种专门为游戏玩家提供陪伴、指导和互动服务的平台。这类系统通常通过专业的陪玩师&#xff08;也称为陪练师&#xff09;为玩家提供一对一或多对一的游戏陪伴服务&#xff0c;帮助玩家提升游戏技能、享受游戏乐趣&#xff0c;甚至解决游戏中的各种问题。电…

关于SpringBoot集成Kafka

关于Kafka Apache Kafka 是一个分布式流处理平台&#xff0c;广泛用于构建实时数据管道和流应用。它能够处理大量的数据流&#xff0c;具有高吞吐量、可持久化存储、容错性和扩展性等特性。 Kafka一般用作实时数据流处理、消息队列、事件架构驱动等 Kafka的整体架构 ZooKeeper:…