OpenHarmony源码转换器—多线程特性转换

本文讨论了如何将多线程的 Java 代码转换为 OpenHarmony ArkTS 代码​

一、简介

Java 内存共享模型

以下示例伪代码和示意图展示了如何使用内存共享模型解决生产者消费者问题。

在这里插入图片描述

生产者消费者与共享内存间交互示意图

为了避免不同生产者或消费者同时访问一块共享内存的容器时产生的脏读,脏写现象,同一时间只能有一个生产者或消费者访问该容器,也就是不同生产者和消费者争夺使用容器的锁。当一个角色获取锁之后其他角色需要等待该角色释放锁之后才能重新尝试获取锁以访问该容器。

// 此段示例为伪代码仅作为逻辑示意,便于开发者理解使用内存共享模型和Actor模型的区别
BufferQueue {
    Queue queue
    Mutex mutex
    add(value) {
        // 尝试获取锁
        if (mutex.lock()) {
            queue.push(value)
            mutex.unlock()
        }
    }

    take() {
        // 尝试获取锁
        if (mutex.lock()) {
            if (queue.empty()) {
                return null
            }
            let res = queue.pop(value)
            mutex.unlock()
            return res
        }
    }
}

// 构造一段全局共享的内存
let g_bufferQueue = new BufferQueue()

Producer {
    run() {
        let value = random()
        // 跨线程访问bufferQueue对象
        g_bufferQueue.add(value)
    }
}

Consumer {
    run() {
        // 跨线程访问bufferQueue对象
        let res = g_bufferQueue.take()
        if (res != null) {
            // 添加消费逻辑
        }
    }
}

Main() {
    let consumer = new Consumer()
    let producer = new Producer()
    // 多线程执行生产任务
    for 0 in 10 :
        let thread = new Thread()
        thread.run(producer.run())
        consumer.run()
}

ArkTS Actor 模型

以下示例简单展示了如何使用基于Actor模型的TaskPool并发能力来解决生产者消费者问题。

Actor模型线程间通信示意图
在这里插入图片描述

Actor模型不同角色之间并不共享内存,生产者线程和UI线程都有自己独占的内存。生产者生产出结果后通过序列化通信将结果发送给UI线程,UI线程消费结果后再发送新的生产任务给生产者线程。

import taskpool from '@ohos.taskpool';
// 跨线程并发任务
@Concurrent
async function produce(): Promise<number>{
  // 添加生产相关逻辑
  console.log("producing...");
  return Math.random();
}

class Consumer {
  public consume(value : number) {
    // 添加消费相关逻辑
    console.log("consuming value: " + value);
  }
}

@Entry
@Component
struct Index {
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
        Button() {
          Text("start")
        }.onClick(() => {
          let produceTask: taskpool.Task = new taskpool.Task(produce);
          let consumer: Consumer = new Consumer();
          for (let index: number = 0; index < 10; index++) {
            // 执行生产异步并发任务
            taskpool.execute(produceTask).then((res : number) => {
              consumer.consume(res);
            }).catch((e : Error) => {
              console.error(e.message);
            })
          }
        })
        .width('20%')
        .height('20%')
      }
      .width('100%')
    }
    .height('100%')
  }
}

Java 开发者仍然习惯于使用基于共享内存的并发模型,上手 actor 并发模型仍有一定的学习成本,但是借助 OpenHarmony源码转换器,Java 开发者可以无痛将代码迁移到 ArkTS,减轻工作负担。

二、方案对比

Java 开发者在处理并发问题时经常会碰到三个问题:可见性、原子性、有序性,好在都有解决方案,比如用锁来保证可见性,原子指令保证原子性,内存屏障保证有序性。正确使用时,并发粒度高,但出现问题时,很难调试复现。

ArkTS 并发模型不基于共享内存,因此并不会存在上述并发法问题,但并发粒度比较低。

OpenHarmony 实际上提供了两种线程机制:TaskPool、Worker。TaskPool 是对 Worker 的封装,开发者不需要去手动管理生命周期。Worker 线程机制最多开启 8 个线程。

TaskPool运作机制

TaskPool运作机制示意图
TaskPool支持开发者在主线程封装任务抛给任务队列,系统选择合适的工作线程,进行任务的分发及执行,再将结果返回给主线程。接口直观易用,支持任务的执行、取消。工作线程数量上限为4。

Worker运作机制

在这里插入图片描述
创建Worker的线程称为宿主线程(不一定是主线程,工作线程也支持创建Worker子线程),Worker自身的线程称为Worker子线程(或Actor线程、工作线程)。每个Worker子线程与宿主线程拥有独立的实例,包含基础设施、对象、代码段等。Worker子线程和宿主线程之间的通信是基于消息传递的,Worker通过序列化机制与宿主线程之间相互通信,完成命令及数据交互。

三、ArkTS 线程当前支持的通信方式

可序列化数据

对于可序列化的数据能够通过Structured Clone算法在线程间进行复制传输。支持的可序列化对象有限:除Symbol之外的基础类型、DateString、RegExp、Array、MapSet、Object (仅限简单对象,比如通过{}或者new Object创建,普通对象仅支持传递属性,不支持传递其原型及方法)、TypedArray。

可转移数据 ArrayBuffer

可转移对象(Transferable object)传输采用地址转移进行序列化,不需要内容拷贝,会将ArrayBuffer的所有权转移给接收该ArrayBuffer的线程,转移后该ArrayBuffer在发送它的线程中变为不可用,不允许再访问
let buffer = new ArrayBuffer(100)

可共享数据 SharedArrayBuffer

共享对象SharedArrayBuffer,拥有固定长度,可以存储任何类型的数据,包括数字、字符串等。

共享对象传输指SharedArrayBuffer支持在多线程之间传递,传递之后的SharedArrayBuffer对象和原始的SharedArrayBuffer对象可以指向同一块内存,进而达到内存共享的目的。

SharedArrayBuffer对象存储的数据在同时被修改时,需要通过原子操作保证其同步性,即下个操作开始之前务必需要等到上个操作已经结束。

let ia = new SharedArrayBuffer(1024): // 定义可共享对象,可以使用Atomics进行操作
ia[37] = 163;
console.log(ia[37]);  // Prints 163, the 38th prime
Atomics.store(ia, 37, 123);
while (Atomics.load(ia, 37) == 163);
console.log(ia[37]);  // Prints 123

四、实现难点

需要考虑到Java如下常用能力

  1. Java线程对共享内存的竞争,例如锁的使用
  2. Java线程之间的同步,例如生产与消费者模型
  3. Java线程之间的协同,例如线程的睡眠、唤醒。
  4. Java synchronize关键字的使用
  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
c 音频转换器是一种用于将音频文件转换为不同格式或编码的工具,使用户能够在不同设备或平台上播放音频文件。其码是指实现这种转换功能的程序代码。 c 音频转换器码通常包含以下几个方面的功能实现: 1. 音频文件读取:码会包含一段代码用于读取输入的音频文件。这部分代码需要处理不同音频格式的文件,并将其解析为内部数据结构或缓冲区。 2. 音频格式转换码中会包含一段代码用于将读取到的音频数据进行格式转换。这可以包括音频编码格式的转换、采样率的转换以及声道数的转换等。 3. 音频编码:如果需要将音频文件以不同编码进行存储或传输,码将包含一段代码用于进行音频编码。这可以包括常见的编码算法如MP3、AAC等。 4. 音频写入:码还需要包含将处理后的音频数据写入目标音频文件的代码。这一部分代码需要处理不同格式和编码的文件写入方式,确保生成的文件能够被常见的音频播放器或设备所识别和播放。 此外,c 音频转换器码还可能包含异常处理、进度控制、用户界面等功能。在实际开发中,还需要考虑性能优化、内存管理和代码可维护性等因素。 总之,c 音频转换器码是一段用于实现音频格式转换的程序代码,涵盖读取、转换、编码和写入等功能。它可以帮助用户将音频文件转换为不同的格式和编码,以适应不同的设备和平台的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱技术的小胡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值