利用native的方式实现跨线程调用

简介

在OpenHarmony应用开发实践中,经常会遇到一些耗时的任务,如I/O操作、域名解析以及复杂计算等。这些任务如果直接在主线程中执行,将会严重阻塞主线程,影响后续任务的正常流程,进而导致用户界面响应延迟甚至卡顿。因此,为了提升代码性能,通常会将这类耗时任务放在子线程中执行。
本文将聚焦于如何利用native的方式实现跨线程调用,即采用线程安全函数和libuv异步I/O工具库这两种策略,来优化程序性能并保持流畅的用户体验。

注意事项

以下将详细阐述如何运用native方式创建子线程以执行耗时任务,并确保与JavaScript的无缝交互。为此,开发者可以利用arkui_napi仓库提供的NAPI(Node-API)接口来实现跨语言调用的桥梁。该NAPI的设计严格遵循Node.js的NAPI规范,以便开发者能够更轻松地理解和使用。
特别强调的是,JavaScript函数通常只能在主线程里调用。如果native侧通过std::thread或pthread创建了子线程,那么napi_env、napi_value以及napi_ref是不能直接在子线程上下文中使用的。为确保正确性,当native端在子线程完成其计算或处理后,若需要回调JavaScript函数,必须先通过线程同步机制将结果传递回主线程,然后才能安全地在主线程环境中调用JavaScript函数。
为解决这一问题,以下将提出两种有效的解决方案。

解决方案

线性安全函数

napi_threadsafe_function 提供了接口来创建一个可以在多线程间共享并安全使用的函数对象。通过这个机制,子线程可以将数据传递给主线程,主线程接收到数据后会调用JavaScript回调函数进行处理。该接口包含用于创建、销毁线程安全函数以及在其之间发送消息和同步数据的方法。使用napi_threadsafe_function的一般步骤包括:

创建线程安全函数: 通过调用napi_create_threadsafe_function()创建一个线程安全函数对象。在此过程中,需要指定一个JavaScript回调函数,该函数将在主线程上执行;同时设定相关的上下文信息,这个上下文可以在多个线程之间共享,可以随时通过调用napi_get_threadsafe_function_context()来获取。此外,还可以选择性地提供一个napi_finalize回调,用于在销毁线程安全函数时执行资源清理操作。

获取使用权: 在开始使用线程安全函数之前,调用napi_acquire_threadsafe_function()函数表明线程已准备就绪,可以开始对该线程安全函数进行操作。

从子线程调用回调: 在子线程中,通过调用napi_call_threadsafe_function()来异步触发JavaScript回调函数,并将所需数据作为参数传递给该回调函数。调用会被排队,并最终在JavaScript主线程上执行。

资源清理: 当线程安全函数不再需要时,应当正确地释放和清理与其关联的资源。通常调用napi_release_threadsafe_function()函数来完成的,该函数会按照预定的策略处理尚未执行完毕的回调,并最终销毁线程安全函数对象。

延长生命周期

在JavaScript层面传递给native层的函数引用,其生命周期仅限于它所在的作用域内。若要确保在超出该作用域后仍能继续使用这个函数引用,需要采取适当的方法来延长其生命周期。
可以通过调用napi_create_reference为JavaScript对象创建一个引用(reference)。这样可以避免对象因垃圾回收机制而被提前释放,从而有效地延长它的生命周期。然而,在创建引用之后,务必牢记要在不再需要该引用时,调用napi_delete_reference来释放引用,以防止内存泄漏问题的发生。
深入理解并妥善管理JavaScript与native接口之间对象的生命周期,对于编写高效且无内存泄漏隐患的代码至关重要。建议开发者进一步研究生命周期管理相关文档和最佳实践,以便更好地掌握。

libuv

libuv是一个基于事件驱动的异步I/O库,对于耗时操作,如果直接在libuv的主循环(event loop)中处理,会阻塞后续任务的执行。为解决这个问题,libuv内部维护了一个线程池,用于执行一些耗时操作,并在这些操作完成后,将回调函数添加回主线程的event loop中等待执行。
默认情况下,libuv提供的线程池包含4个线程作为基本工作单元,但最大线程数可以扩展到128个。通过预先设置环境变量 UV_THREADPOOL_SIZE 的值,可以自定义线程池中的线程数量。当线程池初始化时,会创建相应数量的工作线程,并在每个线程内部运行一个 uv_queue_work 函数。
值得注意的是,libuv 中的线程池是全局共享资源,不论应用中有多少个独立的事件循环实例,它们都共用同一个线程池。这样的设计旨在有效利用系统资源,同时避免因频繁创建和销毁线程带来的开销。

uv_queue_work
 uv_queue_work(uv_loop_t* loop,
               uv_work_t* req,
               uv_work_cb work_cb,
               uv_after_work_cb after_work_cb);

初始化一个工作请求,通过调用uv_queue_work函数,可以安排指定的任务,在与事件循环(event loop)关联的线程池中的一个线程上执行。一旦该任务(即work_cb回调函数)完成其操作,将在事件循环线程中调用另一个回调函数after_work_cb。
各参数的具体意义如下:
loop: 指向事件循环结构体的指针,所有异步操作都在这个事件循环上下文中进行管理。
req: 指向uv_work_t结构体的指针,用于传递给工作请求和回调函数的数据。通常开发者会将自定义数据赋值给req->data成员变量以在回调中使用。
work_cb: 执行实际工作的回调函数,一些耗时的操作可以在此执行,该函数在线程池的一个线程上运行。
after_work_cb: 工作完成后在事件循环线程上调用的回调函数,常用于处理work_cb执行结果或触发进一步的JavaScript层面的操作。
需要注意的是,尽管uv_queue_work方法本身不直接涉及NAPI(Node-API)接口,但当涉及到与JavaScript线程交互时,特别是从native层向JavaScript层传递数据并触发回调时,需要正确地管理napi_value对象的生命周期。这需要合理使用napi_handle_scope和相关接口,来确保在JavaScript回调方法创建的napi_value对象,在整个执行过程中保持有效,并在适当的时候释放资源,以避免内存泄漏问题。

示例代码

下面的示例分别用线程安全函数和libuv实现了native的跨线程调用。该示例在ArkTS端传入的JavaScript回调函数中对变量value进行加10运算,在native侧开启了3个子线程执行业务逻辑,子线程业务逻辑完成之后回到主线程执行ArkTS端传入的JavaScript回调函数,从而完成了对ArkTS端变量value的加30操作。

1.使用线程安全函数

ArkTS实现一个JavaScript回调函数。
参数为param,函数体中对参数param加10后绑定变量value,并返回最新的param值。将回调函数作为参数调用native侧的ThreadSafeTest接口。

 //  src/main/ets/pages/Index.ets
     
 Button("threadSafeTest")
   .width('40%')
   .fontSize(20)
   .onClick(()=> {
     // native使用线程安全函数实现跨线程调用
     entry.ThreadSafeTest((param: number) => {
       param += 10;
       logger.info('ThreadSafeTest js callback value = ', param.toString());
       this.value = param;
       return param;
     }
   )
 }).margin(20)

native主线程中实现一个ThreadSafeTest接口。
接口接收到ArkTS传入的JavaScript回调函数后通过napi_create_threadsafe_function创建一个线程安全函数tsfn,tsfn会回调主线程中的ThreadSafeCallJs,然后在ThreadSafeCallJs中调用ArkTS端传入的JavaScript回调函数。

  //  src/main/cpp/hello.cpp

  napi_threadsafe_function tsfn;   // 线程安全函数
  static int g_cValue;             // 保存value最新的值,作为参数传给js回调函数
  int g_threadNum = 3;             // 线程数

  struct CallbackContext {
      napi_env env = nullptr;
      napi_ref callbackRef = nullptr;
      int retData = 0;
  };

  // 安全函数回调
  static void ThreadSafeCallJs(napi_env env, napi_value js_cb, void *context, void *data)
  {
      CallbackContext *argContent = (CallbackContext *)data;
      if (argContent != nullptr) {
          OH_LOG_INFO(LOG_APP, "ThreadSafeTest CallJs start, retData:[%{public}d]", argContent->retData);
          napi_get_reference_value(env, argContent->callbackRef, &js_cb);
      } else {
          OH_LOG_INFO(LOG_APP, "ThreadSafeTest CallJs argContent is null");
          return;
      }

      napi_valuetype valueType = napi_undefined;
      napi_typeof(env, js_cb, &valueType);
      if (valueType != napi_valuetype::napi_function) {
          OH_LOG_ERROR(LOG_APP, "ThreadSafeTest callback param is not function");
          if (argContent != nullptr) {
              napi_delete_reference(env, argContent->callbackRef);
              delete argContent;
              argContent = nullptr;
              OH_LOG_INFO(LOG_APP, "ThreadSafeTest delete argContent");
          }
          return;
      }
      // 将当前value值作为参数调用js函数
      napi_value argv;
      napi_create_int32(env, g_cValue, &argv);
      napi_value result = nullptr;
      napi_call_function(env, nullptr, js_cb, 1, &argv, &result);
      // g_cValue保存调用js后的返回结果
      napi_get_value_int32(env, result, &g_cValue);
      OH_LOG_INFO(LOG_APP, "ThreadSafeTest CallJs end, [%{public}d]", g_cValue);
      if (argContent != nullptr) {
          napi_delete_reference(env, argContent->callbackRef);
          delete argContent;
          argContent = nullptr;
          OH_LOG_INFO(LOG_APP, "ThreadSafeTest delete argContent end");
      }
  }
  
  // 使用安全函数跨线程调用js函数
  static napi_value ThreadSafeTest(napi_env env, napi_callback_info info)
  {
      size_t argc = 1;
      napi_value js_cb;
      napi_value workName;
      // 获取ArkTS 参数
      napi_get_cb_info(env, info, &argc, &js_cb, nullptr, nullptr);
      // 判断参数类型
      napi_valuetype valueType = napi_undefined;
      napi_typeof(env, js_cb, &valueType);
      if (valueType != napi_valuetype::napi_function) {
      OH_LOG_ERROR(LOG_APP, "ThreadSafeTest callback param is not function");
      return nullptr;
      }
      OH_LOG_INFO(LOG_APP, "ThreadSafeTest current value: [%{public}d]", g_cValue);
  
      // 使用安全线程跨线程调用js 函数
      napi_create_string_utf8(env, "workItem", NAPI_AUTO_LENGTH, &workName);
      // 创建线程安全函数
      napi_create_threadsafe_function(env, js_cb, NULL, workName, 0, 1, NULL, NULL, NULL, ThreadSafeCallJs, &tsfn);

在native子线程中调用线程安全函数。
通过std::thread创建子线程,在子线程中通过napi_call_threadsafe_function调用线程安全函数tsfn,把CallbackContext 结构体数据作为参数传入ThreadSafeCallJs。这里在子线程中进行了简单的业务处理,开发者可以根据自身实际需求进行相应的业务操作。

//   src/main/cpp/hello.cpp

  // 在子线程中调用线程安全函数
  for (int i = 0; i < g_threadNum; i++) {
      // 创建回调参数
      auto asyncContext = new CallbackContext();
      asyncContext->env = env;
      asyncContext->retData = i;
      napi_create_reference(env, js_cb, 1, &asyncContext->callbackRef);
      std::thread t([asyncContext]() {
          // 处理业务逻辑
          OH_LOG_INFO(LOG_APP, "ThreadSafeTest ChildTread start, index:[%{public}d], value: [%{public}d]",
                      asyncContext->retData, g_cValue);
          asyncContext->retData++;
          // 请求线程安全函数
          napi_acquire_threadsafe_function(tsfn);
          // 调用线程安全函数
          napi_call_threadsafe_function(tsfn, asyncContext, napi_tsfn_nonblocking);
          OH_LOG_INFO(LOG_APP, "ThreadSafeTest ChildTread end, index:[%{public}d], value: [%{public}d]",
                      asyncContext->retData, g_cValue);

          /* 以下直接在子线程中调用js函数,会崩溃
          napi_value result = nullptr;
          napi_value argv;
          napi_create_int32(env,g_cValue, &argv);
          napi_call_function(env, nullptr, js_cb, 1, &argv, &result);
        */
      });
      t.join();
  }
  // 释放安全线程
  napi_status status = napi_release_threadsafe_function(tsfn, napi_tsfn_release);
  if (status == napi_status::napi_ok) {
      OH_LOG_INFO(LOG_APP, "ThreadSafeTest napi_tsfn_release success.");
  } else {
      OH_LOG_INFO(LOG_APP, "ThreadSafeTest napi_tsfn_release fail !");
  }

2.使用libuv

ArkTS实现一个JavaScript回调函数。
参数为param,函数体中对参数param加10后绑定变量value,并返回最新的param值。然后将回调函数作为参数调用native侧的UvWorkTest接口。

 //  src/main/ets/pages/Index.ets
     
 Button("libuvTest")
   .width('40%')
   .fontSize(20)
   .onClick(()=> {
     // native使用线程安全函数实现跨线程调用
     entry.UvWorkTest((param: number) => {
       param += 10;
       logger.info('UvWorkTest js callback value = ', param.toString());
       this.value = param;
       return param;
     }
   )
 }).margin(20)

native主线程中实现一个UvWorkTest接口。
接口接收到ArkTS传入的JavaScript回调函数后创建子线程,在子线程的执行函数CallbackUvWorkTest中创建工作任务workReq,通过uv_queue_work将工作任务添加到libuv队列中。

//   src/main/cpp/hello.cpp

  void CallbackUvWorkTest(CallbackContext *context)
  {
      if (context == nullptr) {
          OH_LOG_ERROR(LOG_APP, "UvWorkTest context is nullptr");
          return;
      }
      uv_loop_s *loop = nullptr;
      napi_get_uv_event_loop(context->env, &loop);
      // 创建工作数据结构,自定义数据结构添加在data中
      uv_work_t *workReq = new uv_work_t;
      if (workReq == nullptr) {
          if (context != nullptr) {
              napi_delete_reference(context->env, context->callbackRef);
              delete context;
              OH_LOG_INFO(LOG_APP, "UvWorkTest delete context");
              context = nullptr;
          }
          OH_LOG_ERROR(LOG_APP, "UvWorkTest new uv_work_t fail!");
          return;
      }
      workReq->data = (void *)context;
      // 此打印位于子线程
      OH_LOG_INFO(LOG_APP, "UvWorkTest childThread_1 [%{public}d]", g_cValue);
      // 添加工作任务到libuv的队列中
      uv_queue_work(loop, workReq, WorkCallback, AfterWorkCallback);
  }
  
  // 使用uv_work callback 实现跨线程调用js函数
  static napi_value UvWorkTest(napi_env env, napi_callback_info info)
  {
      size_t argc = 1;
      napi_value argv[1] = {0};
      napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
  
      napi_valuetype valueType = napi_undefined;
      napi_typeof(env, argv[0], &valueType);
      if (valueType != napi_function) {
          OH_LOG_ERROR(LOG_APP, "UvWorkTest param is not function");
          return nullptr;
      }
  
      OH_LOG_INFO(LOG_APP, "UvWorkTest current value:[%{public}d]", g_cValue);
      for (int i = 0; i < g_threadNum; i++) {
          auto asyncContext = new CallbackContext();
          if (asyncContext == nullptr) {
              OH_LOG_ERROR(LOG_APP, "UvWorkTest new asyncContext fail!");
              return nullptr;
          }
          asyncContext->env = env;
          asyncContext->retData = i;
          OH_LOG_INFO(LOG_APP, "UvWorkTest thread begin index:[%{public}d], value:[%{public}d]", i, g_cValue);
          napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef);
          // using callback function on other thread
          std::thread testThread(CallbackUvWorkTest, asyncContext);
          testThread.detach();
          OH_LOG_INFO(LOG_APP, "UvWorkTest thread end index:[%{public}d], value:[%{public}d]", i, g_cValue);
      }
      return nullptr;
  }

实现work_cb与after_work_cb。
work_cb位于子线程中,执行实际的业务逻辑;after_work_cb位于主线程中,通过napi_call_function调用ArkTS端传入的JavaScript回调函数。

  // src/main/cpp/hello.cpp

  void WorkCallback(uv_work_t *workReq)
  {
      // 另外一个子线程,一些耗时操作可以在此进行. 此处不能调用js函数.
      CallbackContext *context = (CallbackContext *)workReq->data;
      if (context != nullptr) {
          OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack1 childThread_2 [%{public}d]", context->retData);
          context->retData++;
          OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack2 childThread_2 [%{public}d]", context->retData);
      } else {
          OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack3 childThread_2 context is null.");
      }
  }
  
  void AfterWorkCallback(uv_work_t *workReq, int status)
  {
      CallbackContext *context = (CallbackContext *)workReq->data;
      // 主线程执行,可以在此调用js函数
      OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack mainThread [%{public}d]", context->retData);
      napi_handle_scope scope = nullptr;
      napi_open_handle_scope(context->env, &scope);
      if (scope == nullptr) {
          if (context != nullptr) {
              napi_delete_reference(context->env, context->callbackRef);
              delete context;
              context = nullptr;
          }
          if (workReq != nullptr) {
              delete workReq;
              workReq = nullptr;
          }
          return;
      }
      napi_value callback = nullptr;
      napi_get_reference_value(context->env, context->callbackRef, &callback);
      napi_value retArg;
      OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack begin [%{public}d]", g_cValue);
      napi_create_int32(context->env, g_cValue, &retArg);
      napi_value ret;
      napi_call_function(context->env, nullptr, callback, 1, &retArg, &ret);
      // 保存js回调结果
      napi_get_value_int32(context->env, ret, &g_cValue);
      OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack end [%{public}d]", g_cValue);
  
      napi_close_handle_scope(context->env, scope);
      if (context != nullptr) {
          napi_delete_reference(context->env, context->callbackRef);
          delete context;
          OH_LOG_INFO(LOG_APP, "UvWorkTest delete context");
          context = nullptr;
      }
      if (workReq != nullptr) {
          delete workReq;
          OH_LOG_INFO(LOG_APP, "UvWorkTest delete work");
          workReq = nullptr;
      }
  }

总结

线程安全函数和libuv方案都是在子线程的执行函数运行结束后回到主线程,并将JavaScript回调函数push到主线程的event-loop队列里等待被执行。
两者的差异在于libuv的子线程属于libuv线程池,而线程安全函数的子线程需要根据业务要求自己创建。另外在libuv中,JavaScript回调函数只能在子线程的主函数执行完毕后被动被执行;而在线程安全函数中,JavaScript回调函数则可以在任意线程中主动调用。

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【学习笔记】Elsevier的Latex模板文件(附网址)

注&#xff1a;这是一篇没有技术含量的水文&#xff0c;主要是看有人下载下来&#xff0c;居然当成资源需要积分才能下载。我觉得不行&#xff0c;故提供原始下载地址供查阅使用。 链接: 上述图片所示网址&#xff1a;链接直达

Redis(性能管理、主从复制、哨兵模式)概述及部署

目录 一、性能管理 1、查看Redis内存使用 2、内存碎片率 3、跟踪内存碎片率 4、内存使用率 5、内回收key 二、Redis集群有三种模式 三、Redis主从复制 1、主从复制的概念 2、主从复制的作用 3、主从复制的流程 4、搭建Redis主从复制 1.环境准备 2.安装Redis&#…

在团队管理过程中,怎样能够让员工更容易接受和执行反馈呢?

让员工更容易接受和执行反馈&#xff0c;关键在于建立一种开放、积极且互相尊重的沟通氛围。 确保反馈的针对性和具体性是关键。 员工需要清楚了解他们的表现如何&#xff0c;以及他们需要改进的地方在哪里。因此&#xff0c;反馈内容应具体明确&#xff0c;避免模糊或笼统的表…

Python零基础到精通

Python入门指南 欢迎进入 Python 领域。作为程序员&#xff0c;我们总是在寻找能提高效率、编写优雅的代码的工具&#xff0c;而 Python 正是其中之一。你即将探索一种强大而灵活的编程语言&#xff0c;它被广泛应用于各种领域&#xff0c;从网站开发到数据科学&#xff0c;再到…

基础总结篇:Activity生命周期

private int param 1; //Activity创建时被调用 Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, “onCreate called.”); setContentView(R.layout.lifecycle); Button btn (Button) findViewById(R.id.…

【开源语音项目OpenVoice](一)——实操演示

目录 一、前菜 1、Python选择 2、pip源切换 3、ffmpeg配置问题 4、VSCode添加Jupyter扩展 二、配置虚拟环境 1、下载源码 方法一 直接下载源码压缩包 方法二 使用git 1&#xff09;git加入鼠标右键 2&#xff09;git clone源码 2、VSCode出场 1&#xff09;创建pyth…

VC++ 错误 Compiler Error C2018 unknown character ‘hexnumber‘

如下图&#xff0c;不能识别的字符0xa1&#xff0c; 这是代码中出现了全角空格&#xff0c;如下图标识的一行&#xff0c;等号前面出现了全角空格&#xff1b;改为半角空格&#xff1b; 如果是未知字符0xa3&#xff0c;则是程序中有中文符号&#xff0c;如程序中的引号&#xf…

【C++算法竞赛 · 图论】图论基础

前言 图论基础 图的相关概念 图的定义 图的分类 按数量分类&#xff1a; 按边的类型分类&#xff1a; 边权 简单图 度 路径 连通 无向图 有向图 图的存储 方法概述 代码 复杂度 前言 图论&#xff08;Graph theory&#xff09;&#xff0c;是 OI 中的一样很大…

小米造车,特斯拉销量不满人意,马斯克坐不住了:将于8月8日推出自动驾驶出租车

在Elon Musk声称路透社关于“放弃2.5万美元低成本电动汽车计划&#xff0c;而将所有精力集中于Robotaxi&#xff08;自动驾驶出租车&#xff09;”上的报道“撒谎”仅几小时后&#xff0c;特斯拉首席执行官在X社交平台上宣布&#xff0c;他将在8月8日的活动中揭示这款所谓的Rob…

Android APP加固利器:深入了解混淆算法与混淆配置

Android APP 加固是优化 APK 安全性的一种方法&#xff0c;常见的加固方式有混淆代码、加壳、数据加密、动态加载等。下面介绍一下 Android APP 加固的具体实现方式。 混淆代码 使用 ipaguard工具可以对代码进行混淆&#xff0c;使得反编译出来的代码很难阅读和理解&#xff…

相对论中关于光速不变理解的补充

近几个月在物理直播间聊爱因斯坦相对论&#xff0c;发现好多人在理解爱因斯坦相对论关于基本假设&#xff0c;普遍认为光速是不变的&#xff0c;质能方程 中光速的光速不变的&#xff0c;在这里我对这个假设需要做一个补充&#xff0c;他是基于质能方程将光速C 在真是光速变化曲…

JavaEE 初阶篇-生产者与消费者模型(线程通信)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 生产者与消费者模型概述 2.0 在生产者与消费者模型中涉及的关键概念 2.1 缓冲区 2.2 生产者 2.3 消费者 2.4 同步机制 2.5 线程间通信 3.0 实现生产者与消费者模…

【Python】 小顶堆:困难 Leetcode 23. 合并 K 个升序链表 -- Python中heapq对于自定义数据类型的比较

描述 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例 1&#xff1a; 输入&#xff1a;lists [[1,4,5],[1,3,4],[2,6]] 输出&#xff1a;[1,1,2,3,4,4,5,6] 代码 代码1 由于可能存在相同…

构造函数和析构函数

目录 一&#xff1a;类的六个默认函数 二&#xff1a;构造函数 2.1概念 2.2特性 三&#xff1a;析构函数 3.1概念&#xff1a; 3.2特性 一&#xff1a;类的六个默认函数 如果一个类中什都没有&#xff0c;称为空类 空类中真的什么都没有吗?并不是&#xff0c;任何类在…

MySQL客户端安装并配置免密登录

最近在写脚本时需要向MySQL数据库中存储数据&#xff0c;且脚本运行的服务器与MySQL服务器不是同一台服务器&#xff0c;而且需要保证MySQL密码的安全性&#xff0c;不能在脚本中暴露&#xff0c;所以就需要在服务器上安装MySQL客户端&#xff0c;并配置免密登录。 一、虚拟机…

C++ List 到 Python List 的转换

当我们编写 C 库的封装器通常涉及使用一种跨语言的接口技术&#xff0c;比如使用C接口或者使用特定的跨语言库&#xff0c;比如SWIG&#xff08;Simplified Wrapper and Interface Generator&#xff09;或者Pybind11。这里我将简要介绍如何使用Pybind11来封装一个C库&#xff…

jenkins+docker实现可持续自动化部署springboot项目

目录 一、前言 二、微服务带来的挑战 2.1 微服务有哪些问题 2.2 微服务给运维带来的挑战 三、可持续集成与交付概述 3.1 可持续集成与交付概念 3.1.1 持续集成 3.1.2 持续交付 3.1.3 可持续集成与交付核心理念 3.2 可持续集成优点 3.3 微服务为什么需要可持续集成 四…

金三银四面试题(十五):Java基础问题(6)

这部分面试题多用于面试的热身运动&#xff0c;对很多找实习和准备毕业找工作的小伙伴至关重要。 HashMap与ConcurrentHashMap 都是key-value 形式的存储数据&#xff1b; HashMap 是线程不安全的&#xff0c;ConcurrentHashMap 是JUC 下的线程安全的&#xff1b; HashMap 底层…

降低笔记本电脑噪音的七种方法,看下有没有适合你的

序言 无论是玩游戏、浏览网络还是做严肃的工作,差不多都有这么一台笔记本电脑,它有足够的处理能力来处理几乎任何事情。不幸的是,它可能会变得非常大声,但有办法来遏制这种噪音。 清洁通风口和风扇,并使用硬表面 如果你的笔记本电脑现在比过去运行同样的软件时声音更大…

docker笔记(二):镜像、容器数据卷

四、 docker镜像 4.1 镜像 镜像是一种轻量级、可执行的独立软件包&#xff0c;用来打包软件运行环境和基于运行环境开发的软件&#xff0c;它包含运行某个软件所需的所有内容&#xff0c;包括代码、库、环境变量和配置文件 所有的应用&#xff0c;直接打包docker镜像就可以直…