HarmonyOS CPU與I/O密集型任務開發指導

HarmonyOS開發者社群發表於2023-09-26

一、CPU密集型任務開發指導

CPU密集型任務是指需要佔用系統資源處理大量計算能力的任務,需要長時間執行,這段時間會阻塞執行緒其它事件的處理,不適宜放在主執行緒進行。例如影像處理、影片編碼、資料分析等。

基於多執行緒併發機制處理CPU密集型任務可以提高CPU利用率,提升應用程式響應速度。

當進行一系列同步任務時,推薦使用Worker;而進行大量或排程點較為分散的獨立任務時,不方便使用8個Worker去做負載管理,推薦採用TaskPool。接下來將以影像直方圖處理以及後臺長時間的模型預測任務分別進行舉例。

使用TaskPool進行影像直方圖處理

1.      實現影像處理的業務邏輯。

2.      資料分段,將各段資料透過不同任務的執行完成影像處理。

建立 Task ,透過 execute() 執行任務,在當前任務結束後,會將直方圖處理結果同時返回。

3.      結果陣列彙總處理。

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%')
  }
}

使用Worker進行長時間資料分析

本文透過某地區提供的房價資料訓練一個簡易的房價預測模型,該模型支援透過輸入房屋面積和房間數量去預測該區域的房價,模型需要長時間執行,房價預測需要使用前面的模型執行結果,因此需要使用Worker。

1.      DevEco Studio提供了Worker建立的模板,新建一個Worker執行緒,例如命名為“MyWorker”。

2.      在主執行緒中透過呼叫ThreadWorker的 constructor() 方法建立Worker物件,當前執行緒為宿主執行緒。

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

3.      在宿主執行緒中透過呼叫 onmessage() 方法接收Worker執行緒傳送過來的訊息,並透過呼叫 postMessage() 方法向Worker執行緒傳送訊息。

例如向Worker執行緒傳送訓練和預測的訊息,同時接收Worker執行緒傳送回來的訊息。

// 接收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.      在MyWorker.ts檔案中繫結Worker物件,當前執行緒為Worker執行緒。

import worker, { ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@ohos.worker';
let workerPort: ThreadWorkerGlobalScope = worker.workerPort;

5.      在Worker執行緒中透過呼叫 onmessage() 方法接收宿主執行緒傳送的訊息內容,並透過呼叫 postMessage() 方法向宿主執行緒傳送訊息。

如在Worker執行緒中定義預測模型及其訓練過程,同時與主執行緒進行資訊互動。

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.      在Worker執行緒中完成任務之後,執行Worker執行緒銷燬操作。銷燬執行緒的方式主要有兩種:根據需要可以在宿主執行緒中對Worker執行緒進行銷燬;也可以在Worker執行緒中主動銷燬Worker執行緒。

在宿主執行緒中透過呼叫 onexit() 方法定義Worker執行緒銷燬後的處理邏輯。

// Worker執行緒銷燬後,執行onexit回撥方法
workerInstance.onexit = function() {
  console.info("main thread terminate");
}

方式一:在宿主執行緒中透過呼叫 terminate() 方法銷燬Worker執行緒,並終止Worker接收訊息。

// 銷燬Worker執行緒
workerInstance.terminate();

方式二:在Worker執行緒中透過呼叫 close() 方法主動銷燬Worker執行緒,並終止Worker接收訊息。

// 銷燬執行緒
workerPort.close();

二、I/O密集型任務開發指導

使用非同步併發可以解決單次I/O任務阻塞的問題,但是如果遇到I/O密集型任務,同樣會阻塞執行緒中其它任務的執行,這時需要使用多執行緒併發能力來進行解決。

I/O密集型任務的效能重點通常不在於CPU的處理能力,而在於I/O操作的速度和效率。這種任務通常需要頻繁地進行磁碟讀寫、網路通訊等操作。此處以頻繁讀寫系統檔案來模擬I/O密集型併發任務的處理。

1.      定義併發函式,內部密集呼叫I/O能力。

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.      使用TaskPool執行包含密集I/O的併發函式:透過呼叫 execute() 方法執行任務,並在回撥中進行排程結果處理。示例中的filePath1和filePath2的獲取方式請參見 獲取應用檔案路徑

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}`);
})


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70009402/viewspace-2986079/,如需轉載,請註明出處,否則將追究法律責任。

相關文章