HarmonyOS 鸿蒙Next多线程并发

HarmonyOS 鸿蒙Next多线程并发

ArkTS多线程下如何实现单例以及保证并发安全

4 回复

还有一个方案:

  • 实现方案介绍 步骤一:使用ArkTS对象定义Sendable类的单例,封装为共享模块(进程内共享),并在子线程中初始化。

    步骤二:初始化完成后通知主线程,主线程使用该单例对象。

  • 业务实现中的关键点 Sendable类需要满足一定的约束,可参考@Sendable装饰器

  • 案例参考

// Demo.ets
"use shared"
@Sendable
export class Demo {
  private static instance: Demo;
  private constructor() {
  }
  public static getInstance(): Demo {
    if (!Demo.instance) {
      Demo.instance = new Demo();
    }
    return Demo.instance;
  }
  public init(): void {
    // initialization logic
  }
}
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { taskpool } from '@kit.ArkTS';
import { Demo } from './demo';
const DOMAIN = 0x0000;
const TAG = 'InterthreadCommunication3';
const FORMAT = '%{public}s';
@Concurrent
function initSingleton(): void {
  let demo = Demo.getInstance();
  demo.init();
  // Notify the main thread that initialization is complete
}
async function executeTaskPool(): Promise<void> {
  let task = new taskpool.Task(initSingleton);
  await taskpool.execute(task).catch((err: BusinessError) => {
    hilog.error(DOMAIN, TAG, FORMAT, `taskpool execute error. Cause code: ${err.code},message: ${err.message}`);
  });
}
executeTaskPool();

更多关于HarmonyOS 鸿蒙Next多线程并发的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


线程间模块共享(单例模式)

  • 场景描述
    进程的唯一ArkTS实例初始化流程复杂,整体耗时较长。如果在主线程中进行初始化,会导致应用启动时间延长并阻塞主线程的执行。因此,建议将这些实例的初始化流程放在ArkTS子线程中进行。初始化完成后,主线程可以直接使用该实例。

    常见的业务场景如下所示:

常见业务场景 具体业务描述
SDK初始化 在ArkTS子线程中调用API的Init初始化得到一个单例对象,完成后传给其他ArkTS线程使用
  • 实现方案介绍
    步骤一:使用C++单例模式封装,并在上层封装JS壳,子线程中进行初始化。

    步骤二:初始化完成后通知主线程,主线程导入并使用该单例对象。

  • 业务实现中的关键点
    a. JS模块对象
    模块定义的导出对象即为使用者导入时获得的对象。

    JS模块对象中的JS函数通过Node-API方法绑定到模块的Native静态方法。调用JS函数时,实际会调用Native静态方法来提供功能。

    b. Native Instance
    模块对象的成员对象(ExternalReference)通过Native Class的GetCurrentInstance(标准单例实现)获取,进程内同模块均指向同一个Native单例。此设计适用于已有线程安全C++类的Native实现,Native成员方法需进行同步保护。

    该模块对象即使包含其他JS成员,也类似于"局部变量",即线程间不共享。

    c. Native静态方法
    Native静态方法提供对应模块的Native功能实现。通过napi_get_cb_info获取JS绑定函数的this对象,从而通过this获取绑定在JS模块对象上的Native实例,再调用Native实例对应的Native成员方法,即可完成对应功能的实现。

    说明

    同上,方法实现中不能进行非线程安全的全局变量操作。

    d. 生命周期问题
    模块对象通常在主线程退出时进行析构。

    若需精细化控制,可以绑定finalizeCallback进行管理。线程对象回收时,该线程会调用析构方法。

  • 案例参考

// napi_init.cpp
class Singleton {
public:
    static Singleton &GetInstance() {
        static Singleton instance;
        return instance;
    }
    static napi_value GetAddress(napi_env env, napi_callback_info info) {
        uint64_t addressVal = reinterpret_cast<uint64_t>(&GetInstance());
        napi_value napiAddress = nullptr;
        napi_create_bigint_uint64(env, addressVal, &napiAddress);
        return napiAddress;
    }
    static napi_value GetSetSize(napi_env env, napi_callback_info info) {
        std::lock_guard<std::mutex> lock(Singleton::GetInstance().numberSetMutex_);
        uint32_t setSize = Singleton::GetInstance().numberSet_.size();
        napi_value napiSize = nullptr;
        napi_create_uint32(env, setSize, &napiSize);
        return napiSize;
    }
    static napi_value Store(napi_env env, napi_callback_info info) {
        size_t argc = 1;
        napi_value args[1] = {nullptr};
        napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
        if (argc != 1) {
            napi_throw_error(env, "ERROR: ", "store args number must be one");
            return nullptr;
        }
        napi_valuetype type = napi_undefined;
        napi_typeof(env, args[0], &type);
        if (type != napi_number) {
            napi_throw_error(env, "ERROR: ", "store args is not number");
            return nullptr;
        }
        std::lock_guard<std::mutex> lock(Singleton::GetInstance().numberSetMutex_);
        uint32_t value = 0;
        napi_get_value_uint32(env, args[0], &value);
        Singleton::GetInstance().numberSet_.insert(value);
        return nullptr;
    }
private:
    Singleton() {}                                    // Private constructor to prevent external instantiation of objects
    Singleton(const Singleton &) = delete;            // Do not copy the constructor
    Singleton &operator=(const Singleton &) = delete; // The assignment operator is prohibited
public:
    std::unordered_set<uint32_t> numberSet_{};
    std::mutex numberSetMutex_{};
};
// Index.ets
import { taskpool } from '@kit.ArkTS';
import singleton from 'libentry.so';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
// ...
@Concurrent
function getAddress() {
  let address = singleton.getAddress();
  hilog.info(0x0000, 'TAG', '%{public}s', 'taskpool:: address is ' + address);
}
@Concurrent
function store(a: number, b: number, c: number) {
  let size = singleton.getSetSize();
  hilog.info(0x0000, 'TAG', '%{public}s', 'set size is ' + size + ' before store');
  singleton.store(a);
  singleton.store(b);
  singleton.store(c);
  size = singleton.getSetSize();
  hilog.info(0x0000, 'TAG', '%{public}s', 'set size is ' + size + ' after store');
}
@Entry
@Component
struct Index {
  build() {
    Row() {
      Column() {
        Button('TestSingleton')
          .onClick(() => {
            let address = singleton.getAddress();
            hilog.info(DOMAIN, TAG, FORMAT, `host thread address is ${address}`);
            let task1 = new taskpool.Task(getAddress);
            taskpool.execute(task1).catch((err: BusinessError) => {
              hilog.error(DOMAIN, TAG, FORMAT,
                `taskpool execute error. Cause code: ${err.code},message: ${err.message}`);
            });
            let task2 = new taskpool.Task(store, 1, 2, 3);
            taskpool.execute(task2).catch((err: BusinessError) => {
              hilog.error(DOMAIN, TAG, FORMAT,
                `taskpool execute error. Cause code: ${err.code},message: ${err.message}`);
            });
            let task3 = new taskpool.Task(store, 4, 5, 6);
            taskpool.execute(task3).catch((err: BusinessError) => {
              hilog.error(DOMAIN, TAG, FORMAT,
                `taskpool execute error. Cause code: ${err.code},message: ${err.message}`);
            });
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

HarmonyOS Next多线程并发基于Actor模型实现线程间通信,通过TaskPool和Worker提供并行任务处理能力。TaskPool适用于无状态任务分发,支持负载均衡;Worker用于有状态任务,维护独立线程上下文。系统提供线程间对象序列化传输机制,支持Promise异步编程模式。并发操作需遵循线程安全规范,通过Sendable接口约束跨线程数据传递。

在HarmonyOS Next的ArkTS多线程环境下,实现单例并保证并发安全可以通过以下方式:

  1. 静态初始化方式(推荐)
class Singleton {
  private static instance: Singleton = new Singleton();
  
  private constructor() {}
  
  public static getInstance(): Singleton {
    return Singleton.instance;
  }
}
  1. 双重检查锁定(适用于延迟初始化)
class Singleton {
  private static instance: Singleton;
  private static lock: object = new Object();
  
  private constructor() {}
  
  public static getInstance(): Singleton {
    if (!Singleton.instance) {
      synchronized(Singleton.lock) {
        if (!Singleton.instance) {
          Singleton.instance = new Singleton();
        }
      }
    }
    return Singleton.instance;
  }
}
  1. 使用@Concurrent装饰器
[@Concurrent](/user/Concurrent)
class Singleton {
  private static instance: Singleton;
  
  private constructor() {}
  
  public static getInstance(): Singleton {
    // 线程安全初始化逻辑
  }
}

关键要点:

  • 将构造函数设为private防止外部实例化
  • 使用synchronized关键字或@Concurrent装饰器保证线程安全
  • 静态初始化方式最简单且线程安全
  • 双重检查锁定减少同步开销

建议优先选择静态初始化方式,它在类加载时就完成初始化,天然线程安全且性能最佳。

回到顶部