HarmonyOS CPU and I/O intensive task development guide

1. Development guidance for CPU-intensive tasks

CPU-intensive tasks refer to tasks that require system resources to process a large amount of computing power and need to run for a long time. During this time, they will block the processing of other events in the thread and are not suitable for execution on the main thread. For example, image processing, video coding, data analysis, etc.

Processing CPU-intensive tasks based on a multi-threaded concurrency mechanism can improve CPU utilization and application response speed.

When performing a series of synchronization tasks, it is recommended to use Worker; when performing a large number of independent tasks or with scattered scheduling points, it is inconvenient to use 8 Workers for load management, and TaskPool is recommended. Next, we will give examples of image histogram processing and long-term model prediction tasks in the background.

Image histogram processing using TaskPool

  1. Implement business logic for image processing.
  2. Data segmentation, each segment of data is executed through different tasks to complete image processing.

Create a Task and execute the task through execute() . After the current task ends, the histogram processing results will be returned at the same time.

     3. Result array summary processing.

import taskpool from '@ohos.taskpool';

@Concurrent
function imageProcessing(dataSlice: ArrayBuffer) {
        
        
  // 步骤1: 具体的图像处理操作及其他耗时操作
  return dataSlice;
}

function histogramStatistic(pixelBuffer: ArrayBuffer) {
        
        
  // 步骤2: 分成三段并发调度
  let number = pixelBuffer.byteLength / 3;
  let buffer1 = pixelBuffer.slice(0, number);
  let buffer2 = pixelBuffer.slice(number, number * 2);
  let buffer3 = pixelBuffer.slice(number * 2);

  let task1 = new taskpool.Task(imageProcessing, buffer1);
  let task2 = new taskpool.Task(imageProcessing, buffer2);
  let task3 = new taskpool.Task(imageProcessing, buffer3);

  taskpool.execute(task1).then((ret: ArrayBuffer[]) => {
        
        
    // 步骤3: 结果处理
  });
  taskpool.execute(task2).then((ret: ArrayBuffer[]) => {
        
        
    // 步骤3: 结果处理
  });
  taskpool.execute(task3).then((ret: ArrayBuffer[]) => {
        
        
    // 步骤3: 结果处理
  });
}

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

  build() {
        
        
    Row() {
        
        
      Column() {
        
        
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
        
        
            let data: ArrayBuffer;
            histogramStatistic(data);
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

Using Workers for long-term data analysis

This article uses the housing price data provided by a certain area to train a simple housing price prediction model. This model supports predicting housing prices in the area by inputting the housing area and number of rooms. The model needs to run for a long time, and the housing price prediction needs to use the previous model running results, so Need to use Worker.

  1. DevEco Studio provides a template created by Worker to create a new Worker thread, for example, named "MyWorker".

       2. Create a Worker object in the main thread by calling the constructor() method of ThreadWorker. The current thread is the host thread.

import worker from '@ohos.worker';

const workerInstance = new worker.ThreadWorker('entry/ets/workers/MyWorker.ts');

     3. In the host thread, receive the message sent by the Worker thread by calling the onmessage() method, and send the message to the Worker thread by calling the postMessage() method.

For example, send training and prediction messages to the Worker thread, and receive messages sent back by the Worker thread at the same time.

// 接收Worker子线程的结果
workerInstance.onmessage = function(e) {
        
        
  // data:主线程发送的信息
  let data = e.data;
  console.info('MyWorker.ts onmessage');
  // 在Worker线程中进行耗时操作
}

workerInstance.onerror = function (d) {
        
        
  // 接收Worker子线程的错误信息
}

// 向Worker子线程发送训练消息
workerInstance.postMessage({
        
         'type': 0 });
// 向Worker子线程发送预测消息
workerInstance.postMessage({
        
         'type': 1, 'value': [90, 5] });

     4. Bind the Worker object in the MyWorker.ts file, and the current thread is the Worker thread.

import worker, {
        
         ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@ohos.worker';

let workerPort: ThreadWorkerGlobalScope = worker.workerPort;

     5. In the Worker thread, receive the message content sent by the host thread by calling the onmessage() method, and send the message to the host thread by calling the postMessage() method.

For example, the prediction model and its training process are defined in the Worker thread, and information is interacted with the main thread at the same time.

import worker, {
        
         ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@ohos.worker';

let workerPort: ThreadWorkerGlobalScope = worker.workerPort;

// 定义训练模型及结果 
let result;

// 定义预测函数
function predict(x) {
        
        
  return result[x];
}

// 定义优化器训练过程
function optimize() {
        
        
  result = {};
}

// Worker线程的onmessage逻辑
workerPort.onmessage = function (e: MessageEvents) {
        
        
  let data = e.data
  // 根据传输的数据的type选择进行操作
  switch (data.type) {
        
        
    case 0:
    // 进行训练
      optimize();
    // 训练之后发送主线程训练成功的消息
      workerPort.postMessage({
        
         type: 'message', value: 'train success.' });
      break;
    case 1:
    // 执行预测
      const output = predict(data.value);
    // 发送主线程预测的结果
      workerPort.postMessage({
        
         type: 'predict', value: output });
      break;
    default:
      workerPort.postMessage({
        
         type: 'message', value: 'send message is invalid' });
      break;
  }
}

       6. After completing the task in the Worker thread, perform the Worker thread destruction operation. There are two main ways to destroy threads: the Worker thread can be destroyed in the host thread as needed; the Worker thread can also be actively destroyed in the Worker thread.

Define the processing logic after the Worker thread is destroyed by calling the onexit() method in the host thread .

// Worker线程销毁后,执行onexit回调方法
workerInstance.onexit = function() {
        
        
  console.info("main thread terminate");
}

Method 1: Destroy the Worker thread by calling the terminate() method in the host thread and terminate the Worker from receiving messages.

// 销毁Worker线程
workerInstance.terminate();

Method 2: Actively destroy the Worker thread by calling the close() method in the Worker thread, and terminate the Worker from receiving messages.

// 销毁线程
workerPort.close();

2. I/O-intensive task development guidance

Using asynchronous concurrency can solve the problem of blocking a single I/O task. However, if an I/O-intensive task is encountered, it will also block the execution of other tasks in the thread. In this case, multi-thread concurrency capabilities need to be used to solve the problem.

The performance focus of I/O-intensive tasks is usually not on the processing power of the CPU, but on the speed and efficiency of I/O operations. This kind of task usually requires frequent disk reading and writing, network communication and other operations. Here, frequent reading and writing of system files are used to simulate the processing of I/O-intensive concurrent tasks.

  1. Define concurrent functions and intensively call I/O capabilities internally.
import fs from '@ohos.file.fs';

// 定义并发函数,内部密集调用I/O能力
@Concurrent
async function concurrentTest(fileList: string[]) {
        
        
  // 写入文件的实现
  async function write(data, filePath) {
        
        
    let file = await fs.open(filePath, fs.OpenMode.READ_WRITE);
    await fs.write(file.fd, data);
    fs.close(file);
  }
  // 循环写文件操作
  for (let i = 0; i < fileList.length; i++) {
        
        
    write('Hello World!', fileList[i]).then(() => {
        
        
      console.info(`Succeeded in writing the file. FileList: ${fileList[i]}`);
    }).catch((err) => {
        
        
      console.error(`Failed to write the file. Code is ${err.code}, message is ${err.message}`)
      return false;
    })
  }
  return true;
}

       2. Use TaskPool to execute concurrent functions containing intensive I/O: execute the task by calling the execute() method, and process the scheduling results in the callback. For how to obtain filePath1 and filePath2 in the example, see Obtaining the Application File Path .

import taskpool from '@ohos.taskpool';

let filePath1 = ...; // 应用文件路径
let filePath2 = ...;

// 使用TaskPool执行包含密集I/O的并发函数
// 数组较大时,I/O密集型任务任务分发也会抢占主线程,需要使用多线程能力
taskpool.execute(concurrentTest, [filePath1, filePath2]).then((ret) => {
        
        
  // 调度结果处理
  console.info(`The result: ${ret}`);
})

Guess you like

Origin blog.csdn.net/HarmonyOSDev/article/details/133311960