利用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.etsButton("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.cppnapi_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.etsButton("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.cppvoid 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 threadstd::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.cppvoid 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.鸿蒙南向开发方向

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【鸿蒙 HarmonyOS】获取设备的地理位置

一、背景 获取移动设备的地理位置&#xff0c;包含&#xff1a;经度、维度、具体地理位置等&#xff0c;地理位置信息能在许多业务场景中被应用&#xff0c;如导航、地图服务、位置服务、社交媒体等。 下面以一个Demo例子&#xff0c;来实现获取设备地理位置的功能 官方文档…

软件测试下的AI之路(4)

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

实时计算平台设计方案:913-基于100G光口的DSP+FPGA实时计算平台

基于100G光口的DSPFPGA实时计算平台 一、产品概述 基于以太网接口的实时数据智能计算一直应用于互联网、网络安全、大数据交换的场景。以DSPFPGA的方案&#xff0c;体现了基于硬件计算的独特性能&#xff0c;区别于X86GPU的计算方案&#xff0c;保留了高带宽特性&…

Ceph学习 - 1.存储知识

文章目录 1.存储基础1.1 基础知识1.1.1 存储基础1.1.2 存储使用 1.2 文件系统1.2.1 简介1.2.2 数据存储1.2.3 存储应用的基本方式1.2.4 文件存储 1.3 小结 1.存储基础 学习目标&#xff1a;这一节&#xff0c;我们从基础知识、文件系统、小节三个方面来学习。 1.1 基础知识 1.…

UART设计

一、UART通信简介 通用异步收发器&#xff0c; 特点&#xff1a;串行、异步、全双工通信 优点&#xff1a;通信线路简单&#xff0c;传输距离远 缺点&#xff1a;传输速度慢 数据传输速率&#xff1a;波特率&#xff08;单位&#xff1a;baud&#xff0c;波特&#xff09; …

如何高效学习Python编程语言

理解Python的应用场景 不同的编程语言有不同的发展历史和应用场景,了解Python主要应用在哪些领域对于学习它会有很大帮助。Python最初是一种通用脚本语言,主要用于系统级任务自动化。随着时间的推移,它逐步成为数据处理、科学计算、Web开发、自动化运维等众多领域的主要编程语…

Navicat设置mysql权限

新建用户&#xff1a; 注意&#xff1a;如果不生效执行刷新命令:FLUSH PRIVILEGES; 执行后再重新打开查看&#xff1b; 查询权限命令&#xff1a;1234为新建的用户名&#xff0c;localhost为访问的地址 SHOW GRANTS FOR 1234localhost;如果服务器设置服务器权限后可能会出现权…

潜伏三年,核弹级危机一触即发,亚信安全深度分析XZ Utils后门事件

2024年3月29日星期五上午8点&#xff0c;有研究人员称xz/liblzma中的后门导致SSH服务器内存泄露&#xff0c;使得SSH服务异常&#xff08;https://www.openwall.com/lists/oss-security/2024/03/29/4&#xff09;。github中“xz”压缩工具主要由Larhzu和Jia Tan共同负责维护&am…

力扣25. K 个一组翻转链表

Problem: 25. K 个一组翻转链表 文章目录 题目描述思路复杂度Code 题目描述 思路 1.创建虚拟头节点dummy并将其next指针指向head&#xff0c;创建指针pre、end均指向dummy&#xff1b; 2.编写反转单链表的函数reverse 3.当end -> next 不为空时&#xff1a; 3.1.每次k个一组…

Bigtable [OSDI‘06] 论文阅读笔记

原论文&#xff1a;Bigtable: A Distributed Storage System for Structured Data (OSDI’06) 1. Introduction Bigtable 是一种用于管理结构化数据的分布式存储系统&#xff0c;可扩展到非常大的规模&#xff1a;数千台服务器上的数据量可达 PB 级别&#xff0c;同时保证可靠…

阿里巴巴拍立淘API新功能揭秘:图片秒搜商品,实现智能化个性化购物新体验

在数字化快速发展的今天&#xff0c;智能化和个性化已经成为购物体验中不可或缺的元素。为了满足消费者日益增长的购物需求&#xff0c;阿里巴巴中国站不断推陈出新&#xff0c;其中拍立淘API的新功能——图片秒搜商品&#xff0c;无疑为智能化个性化购物体验开创了新的篇章。 …

【机器学习入门】使用YOLO模型进行物体检测

系列文章目录 第1章 专家系统 第2章 决策树 第3章 神经元和感知机 识别手写数字——感知机 第4章 线性回归 第5章 逻辑斯蒂回归和分类 第5章 支持向量机 第6章 人工神经网络(一) 第6章 人工神经网络(二) 卷积和池化 第6章 使用pytorch进行手写数字识别 文章目录 系列文章目录前…

八股面试速成—计算机网络部分

暑期实习面试在即&#xff0c;这几天八股和算法轮扁我>_ 八股部分打算先找学习视屏跟着画下思维导图&#xff0c;然后看详细的面试知识点&#xff0c;最后刷题 其中导图包含的是常考的题&#xff0c;按照思维导图形式整理&#xff0c;会在复盘后更新 细节研究侧重补全&a…

基于单片机冬季供暖室温调节控制系统

**单片机设计介绍&#xff0c;基于单片机冬季供暖室温调节控制系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的冬季供暖室温调节控制系统是一种集温度检测、控制和显示功能于一体的智能化系统。该系统以单片机为…

C++ 指针与结构

三种存取结构成员的方式&#xff1a; ① 通过结构变量名&#xff1b; ②通过指向结构的指针和间接运算符(*)&#xff1b; ③通过指向结构的指针和指向成员运算符(->);

【已解决】ZIP压缩文件如何设置密码?

ZIP是常用的压缩格式之一&#xff0c;对于重要的ZIP文件&#xff0c;我们还可设置密码保护&#xff0c;那ZIP压缩文件怎么设置密码呢&#xff1f;不清楚的小伙伴一起来看看吧&#xff01; 给ZIP文件设置密码&#xff0c;我们需要用到支持ZIP格式的解压缩软件&#xff0c;比如7…

2024HW-->Wireshark攻击流量分析

在HW中&#xff0c;最离不开的&#xff0c;肯定是看监控了&#xff0c;那么就要去了解一些wireshark的基础用法以及攻击的流量&#xff01;&#xff01;&#xff01;&#xff01; 1.Wireshark的基本用法 比如人家面试官给你一段流量包&#xff0c;你要会用 1.分组详情 对于我…

UE4_如果快速做出毛玻璃效果_假景深

UE4_如果快速做出毛玻璃效果_假景深 2022-08-20 15:02 一个SpiralBlur-SceneTexture材质节点完成效果&#xff0c;启用半透明材质通过修改BlurAmount数值大小调整效果spiralBlur-SceneTexture custom节点&#xff0c;HLSL语言float3 CurColor 0;float2 BaseUV MaterialFloa…

pytest的时候输出一个F后面跟很多绿色的点解读

使用pytest来测试pyramid和kotti项目&#xff0c;在kotti项目测试的时候&#xff0c;输出一个F后面跟很多绿色的点&#xff0c;是什么意思呢&#xff1f; 原来在使用pytest进行测试时&#xff0c;输出中的“F”代表一个失败的测试&#xff08;Failed&#xff09;&#xff0c;而…

隧道风速风向检测器的工作原理

TH-SQX1隧道风速风向检测器是一种专门用于隧道内部风速和风向监测的设备。它基于超声波技术进行测量&#xff0c;通过发射和接收超声波信号&#xff0c;利用信号传输时间差来精确测量风速和风向。这种检测器具有测量准确、响应速度快、稳定性好等优点&#xff0c;适用于隧道内部…