HarmonyOS:多线程并发-Worker
Worker主要作用是为应用程序提供一个多线程的运行环境,可满足应用程序在执行过程中与宿主线程分离,在后台线程中运行一个脚本进行耗时操作,极大避免类似于计算密集型或高延迟的任务阻塞宿主线程的运行。具体接口信息及使用方法详情请见Worker。
一、Worker运作机制
Worker运作机制示意图
创建Worker的线程称为宿主线程(不一定是主线程,工作线程也支持创建Worker子线程),Worker自身的线程称为Worker子线程(或Actor线程、工作线程)。每个Worker子线程与宿主线程拥有独立的实例,包含基础设施、对象、代码段等,因此每个Worker启动存在一定的内存开销,需要限制Worker的子线程数量。Worker子线程和宿主线程之间的通信是基于消息传递的,Worker通过序列化机制与宿主线程之间相互通信,完成命令及数据交互。
二、Worker注意事项
- 创建Worker时,有手动和自动两种创建方式,手动创建Worker线程目录及文件时,还需同步进行相关配置,详情请参考创建Worker的注意事项。
- 使用Worker能力时,构造函数中传入的Worker线程文件的路径在不同版本有不同的规则,详情请参见文件路径注意事项。
- Worker创建后需要手动管理生命周期,且最多同时运行的Worker子线程数量为64个,详情请参见生命周期注意事项。
- 由于不同线程中上下文对象是不同的,因此Worker线程只能使用线程安全的库,例如UI相关的非线程安全库不能使用。
- 序列化传输的数据量大小限制为16MB。
- 使用Worker模块时,需要在宿主线程中注册onerror接口,否则当Worker线程出现异常时会发生jscrash问题。
- 不支持跨HAP使用Worker线程文件。
- 引用HAR/HSP前,需要先配置对HAR/HSP的依赖,详见引用共享包。
- 不支持在Worker工作线程中使用AppStorage。
三、创建Worker的注意事项
Worker线程文件需要放在"{moduleName}/src/main/ets/"目录层级之下,否则不会被打包到应用中。有手动和自动两种创建Worker线程目录及文件的方式。
- 手动创建:开发者手动创建相关目录及文件,此时需要配置build-profile.json5的相关字段信息,Worker线程文件才能确保被打包到应用中。
Stage模型:
"buildOption": {"sourceOption": {"workers": ["./src/main/ets/workers/worker.ets"]}
}
FA模型:
"buildOption": {"sourceOption": {"workers": ["./src/main/ets/MainAbility/workers/worker.ets"]}
}
- 自动创建:DevEco Studio支持一键生成Worker,在对应的{moduleName}目录下任意位置,点击鼠标右键 > New > Worker,即可自动生成Worker的模板文件及配置信息,无需再手动在build-profile.json5中进行相关配置。
四、文件路径注意事项
当使用Worker模块具体功能时,均需先构造Worker实例对象,其构造函数与API版本相关,且构造函数需要传入Worker线程文件的路径(scriptURL)。
// 导入模块
import { worker } from '@kit.ArkTS';// API 9及之后版本使用:
const worker1: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/worker.ets');
// API 8及之前版本使用:
const worker2: worker.Worker = new worker.Worker('entry/ets/workers/worker.ets');
4.1 Stage模型下的文件路径规则
构造函数中的scriptURL要求如下:
- scriptURL的组成包含 {moduleName}/ets 和相对路径 relativePath。
- relativePath是Worker线程文件相对于"{moduleName}/src/main/ets/"目录的相对路径。
4.1.1 加载Ability中Worker线程文件场景
加载Ability中的worker线程文件,加载路径规则:{moduleName}/ets/{relativePath}。
import { worker } from '@kit.ArkTS';// worker线程文件所在路径:"entry/src/main/ets/workers/worker.ets"
const workerStage1: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/worker.ets');// worker线程文件所在路径:"phone/src/main/ets/ThreadFile/workers/worker.ets"
const workerStage2: worker.ThreadWorker = new worker.ThreadWorker('phone/ets/ThreadFile/workers/worker.ets');
4.1.2 加载HSP中Worker线程文件场景
加载HSP中worker线程文件,加载路径规则:{moduleName}/ets/{relativePath}。
import { worker } from '@kit.ArkTS';// worker线程文件所在路径: "hsp/src/main/ets/workers/worker.ets"
const workerStage3: worker.ThreadWorker = new worker.ThreadWorker('hsp/ets/workers/worker.ets');
4.1.3 加载HAR中Worker线程文件场景
加载HAR中worker线程文件存在以下两种情况:
- @标识路径加载形式:所有种类的模块加载本地HAR中的Worker线程文件,加载路径规则:@{moduleName}/ets/{relativePath}。
- 相对路径加载形式:本地HAR加载该包内的Worker线程文件,加载路径规则:创建Worker对象所在文件与Worker线程文件的相对路径。
说明
当开启useNormalizedOHMUrl(即将工程目录中与entry同级别的应用级build-profile.json5文件中strictMode属性的useNormalizedOHMUrl字段配置为true)或HAR包会被打包成三方包使用时,则HAR包中使用Worker仅支持通过相对路径的加载形式创建。
import { worker } from '@kit.ArkTS';// @标识路径加载形式:
// worker线程文件所在路径: "har/src/main/ets/workers/worker.ets"
const workerStage4: worker.ThreadWorker = new worker.ThreadWorker('@har/ets/workers/worker.ets');// 相对路径加载形式:
// worker线程文件所在路径: "har/src/main/ets/workers/worker.ets"
// 创建Worker对象的文件所在路径:"har/src/main/ets/components/mainpage/MainPage.ets"
const workerStage5: worker.ThreadWorker = new worker.ThreadWorker('../../workers/worker.ets');
4.2 FA模型下的文件路径规则
构造函数中的scriptURL为:Worker线程文件与"{moduleName}/src/main/ets/MainAbility"的相对路径。
import { worker } from '@kit.ArkTS';// 主要说明以下三种场景:// 场景1: Worker线程文件所在路径:"{moduleName}/src/main/ets/MainAbility/workers/worker.ets"
const workerFA1: worker.ThreadWorker = new worker.ThreadWorker("workers/worker.ets", {name:"first worker in FA model"});// 场景2: Worker线程文件所在路径:"{moduleName}/src/main/ets/workers/worker.ets"
const workerFA2: worker.ThreadWorker = new worker.ThreadWorker("../workers/worker.ets");// 场景3: Worker线程文件所在路径:"{moduleName}/src/main/ets/MainAbility/ThreadFile/workers/worker.ets"
const workerFA3: worker.ThreadWorker = new worker.ThreadWorker("ThreadFile/workers/worker.ets");
五、生命周期注意事项
- Worker的创建和销毁耗费性能,建议开发者合理管理已创建的Worker并重复使用。Worker空闲时也会一直运行,因此当不需要Worker时,可以调用terminate()接口或close()方法主动销毁Worker。若Worker处于已销毁或正在销毁等非运行状态时,调用其功能接口,会抛出相应的错误。
- Worker的数量由内存管理策略决定,设定的内存阈值为1.5GB和设备物理内存的60%中的较小者。在内存允许的情况下,系统最多可以同时运行64个Worker。如果尝试创建的Worker数量超出这一上限,系统将抛出错误:“Worker initialization failure, the number of workers exceeds the maximum.”。实际运行的Worker数量会根据当前内存使用情况动态调整。一旦所有Worker和主线程的累积内存占用超过了设定的阈值,系统将触发内存溢出(OOM)错误,导致应用程序崩溃。
六、跨har包加载Worker
- 创建har详情参考开发静态共享包。
- 在har中创建Worker线程文件相关内容。
// worker.ets
workerPort.onmessage = (e: MessageEvents) => {console.info("worker thread receive message: ", e.data);workerPort.postMessage('worker thread post message to main thread');
}
- 在entry模块的oh-package.json5文件中配置har包的依赖。
// 在entry模块配置har包的依赖
{"name": "entry","version": "1.0.0","description": "Please describe the basic information.","main": "","author": "","license": "","dependencies": {"har": "file:../har"}
}
- 在entry模块中加载har包中的Worker线程文件。
// Index.ets
import { worker } from '@kit.ArkTS';@Entry
@Component
struct Index {@State message: string = 'Hello World';build() {RelativeContainer() {Text(this.message).id('HelloWorld').fontSize(50).fontWeight(FontWeight.Bold).alignRules({center: { anchor: '__container__', align: VerticalAlign.Center },middle: { anchor: '__container__', align: HorizontalAlign.Center }}).onClick(() => {// 通过@标识路径加载形式,加载har中Worker线程文件let workerInstance = new worker.ThreadWorker('@har/ets/workers/worker.ets');workerInstance.onmessage = () => {console.info('main thread onmessage');};workerInstance.postMessage('hello world');})}.height('100%').width('100%')}
}
七、多级Worker生命周期管理
由于支持创建多级Worker(即通过父Worker创建子Worker的机制形成层级线程关系),且Worker线程生命周期由用户自行管理,因此需要注意多级Worker生命周期的正确管理。若用户销毁父Worker时未能结束其子Worker的运行,会产生不可预期的结果。建议用户确保子Worker的生命周期始终在父Worker生命周期范围内,并在销毁父Worker前先销毁所有子Worker。
7.1 推荐使用示例
// 在主线程中创建Worker线程(父Worker),在worker线程中再次创建Worker线程(子Worker)
// main thread
import { worker, MessageEvents, ErrorEvent } from '@kit.ArkTS';// 主线程中创建父worker对象
const parentworker = new worker.ThreadWorker("entry/ets/workers/parentworker.ets");parentworker.onmessage = (e: MessageEvents) => {console.info("主线程收到父worker线程信息 " + e.data);
}parentworker.onexit = () => {console.info("父worker退出");
}parentworker.onerror = (err: ErrorEvent) => {console.info("主线程接收到父worker报错 " + err);
}parentworker.postMessage("主线程发送消息给父worker-推荐示例");
// parentworker.ets
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';// 创建父Worker线程中与主线程通信的对象
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;workerPort.onmessage = (e : MessageEvents) => {if (e.data == "主线程发送消息给父worker-推荐示例") {let childworker = new worker.ThreadWorker("entry/ets/workers/childworker.ets");childworker.onmessage = (e: MessageEvents) => {console.info("父Worker收到子Worker的信息 " + e.data);if (e.data == "子Worker向父Worker发送信息") {workerPort.postMessage("父Worker向主线程发送信息");}}childworker.onexit = () => {console.info("子Worker退出");// 子Worker退出后再销毁父WorkerworkerPort.close();}childworker.onerror = (err: ErrorEvent) => {console.info("子Worker发生报错 " + err);}childworker.postMessage("父Worker向子Worker发送信息-推荐示例");}
}
// childworker.ets
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';// 创建子Worker线程中与父Worker线程通信的对象
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;workerPort.onmessage = (e: MessageEvents) => {if (e.data == "父Worker向子Worker发送信息-推荐示例") {// 子Worker线程业务逻辑...console.info("业务执行结束,然后子Worker销毁");workerPort.close();}
}
执行结果
7.2 不推荐使用示例
不建议父Worker主动销毁后,子Worker仍向父Worker发送消息。
// main thread
import { worker, MessageEvents, ErrorEvent } from '@kit.ArkTS';const parentworker = new worker.ThreadWorker("entry/ets/workers/parentworker.ets");parentworker.onmessage = (e: MessageEvents) => {console.info("主线程收到父Worker信息" + e.data);
}parentworker.onexit = () => {console.info("父Worker退出");
}parentworker.onerror = (err: ErrorEvent) => {console.info("主线程接收到父Worker报错 " + err);
}parentworker.postMessage("主线程发送消息给父Worker");
// parentworker.ets
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';const workerPort: ThreadWorkerGlobalScope = worker.workerPort;workerPort.onmessage = (e : MessageEvents) => {console.info("父Worker收到主线程的信息 " + e.data);let childworker = new worker.ThreadWorker("entry/ets/workers/childworker.ets")childworker.onmessage = (e: MessageEvents) => {console.info("父Worker收到子Worker的信息 " + e.data);}childworker.onexit = () => {console.info("子Worker退出");workerPort.postMessage("父Worker向主线程发送信息");}childworker.onerror = (err: ErrorEvent) => {console.info("子Worker发生报错 " + err);}childworker.postMessage("父Worker向子Worker发送信息");// 创建子Worker后,销毁父WorkerworkerPort.close();
}
不建议在明确父Worker发起销毁操作的同步调用前后仍在父Worker线程创建子Worker。不建议在不确定父Worker是否发起销毁操作的情况下,仍在父Worker线程创建子Worker,即创建子Worker线程成功之前需保证父Worker线程始终处于存活状态。
// main thread
import { worker, MessageEvents, ErrorEvent } from '@kit.ArkTS';const parentworker = new worker.ThreadWorker("entry/ets/workers/parentworker.ets");parentworker.onmessage = (e: MessageEvents) => {console.info("主线程收到父Worker信息" + e.data);
}parentworker.onexit = () => {console.info("父Worker退出");
}parentworker.onerror = (err: ErrorEvent) => {console.info("主线程接收到父Worker报错 " + err);
}parentworker.postMessage("主线程发送消息给父Worker");
// parentworker.ets
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';const workerPort: ThreadWorkerGlobalScope = worker.workerPort;workerPort.onmessage = (e : MessageEvents) => {console.info("父Worker收到主线程的信息 " + e.data);// 父Worker销毁后创建子Worker,行为不可预期workerPort.close();let childworker = new worker.ThreadWorker("entry/ets/workers/childworker.ets");// 子Worker线程未确认创建成功前销毁父Worker,行为不可预期// let childworker = new worker.ThreadWorker("entry/ets/workers/childworker.ets");// workerPort.close();childworker.onmessage = (e: MessageEvents) => {console.info("父Worker收到子Worker的信息 " + e.data);}childworker.onexit = () => {console.info("子Worker退出");workerPort.postMessage("父Worker向主线程发送信息");}childworker.onerror = (err: ErrorEvent) => {console.info("子Worker发生报错 " + err);}childworker.postMessage("父Worker向子Worker发送信息");
}
// childworker.ets
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';const workerPort: ThreadWorkerGlobalScope = worker.workerPort;workerPort.onmessage = (e: MessageEvents) => {console.info("子Worker收到信息 " + e.data);
}
八、ThreadWorker
使用以下方法前,均需先构造ThreadWorker实例,ThreadWorker类继承WorkerEventTarget。
8.1 constructor
constructor(scriptURL: string, options?: WorkerOptions)
ThreadWorker构造函数。
- 元服务API:从API version 11 开始,该接口支持在元服务中使用。
- 系统能力: SystemCapability.Utils.Lang
参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| script URL | string | 是 | Worker线程文件的路径。路径规则详细参考文件路径注意事项。 |
| options | WorkerOptions | 否 | Worker构造的选项。 |
示例:
此处以在Stage模型中Ability加载Worker文件为例,使用Library加载Worker线程文件的场景参考文件路径注意事项。
import { worker } from '@kit.ArkTS';// 主要说明以下两种场景:// 场景1: worker文件所在路径:"entry/src/main/ets/workers/worker.ets"
const workerStageModel01 = new worker.ThreadWorker('entry/ets/workers/worker.ets', {name:"first worker in Stage model"});// 场景2: worker文件所在路径:"phone/src/main/ets/ThreadFile/workers/worker.ets"
const workerStageModel02 = new worker.ThreadWorker('phone/ets/ThreadFile/workers/worker.ets');
九、postMessage
postMessage(message: Object, transfer: ArrayBuffer[]): void
宿主线程通过转移对象所有权的方式向Worker线程发送消息。
系统能力: SystemCapability.Utils.Lang
元服务API:从API version 11 开始,该接口支持在元服务中使用。
参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| message | Object | 是 | 发送至Worker的数据,该数据对象必须是可序列化,序列化支持类型见其他说明。 |
| transfer | ArrayBuffer[] | 是 | 表示可转移的ArrayBuffer实例对象数组,该数组中对象的所有权会被转移到Worker线程,在宿主线程中将会变为不可用,仅在Worker线程中可用,数组不可传入null。 |
示例:
Worker.ets代码
// Worker.ets
import { worker, MessageEvents, ErrorEvent } from '@kit.ArkTS';// 创建worker线程中与宿主线程通信的对象
const workerPort = worker.workerPort// worker线程接收宿主线程信息
workerPort.onmessage = (e: MessageEvents): void => {// data:宿主线程发送的信息let data: number = e.data;// 往收到的buffer里写入数据const view = new Int8Array(data).fill(3);// worker线程向宿主线程发送信息workerPort.postMessage(view);
}// worker线程发生error的回调
workerPort.onerror = (err: ErrorEvent) => {console.log("worker.ets onerror" + err.message);
}
Index.ets代码
// Index.ets
import { worker, MessageEvents, ErrorEvent } from '@kit.ArkTS';@Entry
@Component
struct Index {@State message: string = 'Hello World';build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold).onClick(() => {// 宿主线程中创建Worker对象const workerInstance = new worker.ThreadWorker("entry/ets/workers/Worker.ets");// 宿主线程向worker线程传递信息const buffer = new ArrayBuffer(8);workerInstance.postMessage(buffer, [buffer]);// 宿主线程接收worker线程信息workerInstance.onmessage = (e: MessageEvents): void => {// data:worker线程发送的信息let data: number = e.data;console.info("main thread data is " + data);// 销毁Worker对象workerInstance.terminate();}// 在调用terminate后,执行onexitworkerInstance.onexit = (code) => {console.log("main thread terminate");}workerInstance.onerror = (err: ErrorEvent) => {console.log("main error message " + err.message);}})}.width('100%').height('100%')}}
}
执行结果
相关文章:
HarmonyOS:多线程并发-Worker
Worker主要作用是为应用程序提供一个多线程的运行环境,可满足应用程序在执行过程中与宿主线程分离,在后台线程中运行一个脚本进行耗时操作,极大避免类似于计算密集型或高延迟的任务阻塞宿主线程的运行。具体接口信息及使用方法详情请见Worker…...
小程序IOS安全区域优化:safe-area-inset-bottom
ios下边有一个小黑线,位于底部的元素会被黑线阻挡 safe-area-inset-bottom 一 用法及作用: IOS全面屏底部有小黑线,位于底部的元素会被黑线阻挡,可以使用以下样式: .model{padding-bottom: constant(safe-area-ins…...
C++ 中多态性在实际项目中的应用场景
C中的多态性是面向对象编程中的一个核心概念,它允许我们在使用基类指针或引用的情况下,调用派生类对象的特定方法。这种特性在实际项目中有着广泛的应用场景,具体包括但不限于以下几个方面: 1.图形图像处理: 在图形图…...
prettier配置
配置 Prettier 在 VSCode 中自动格式化代码的教程 1. 安装 Prettier VSCode 插件 打开 VSCode。点击左侧活动栏的扩展市场图标(或按 Ctrl+Shift+X)。在搜索栏中输入 Prettier - Code formatter。找到插件并点击 Install 安装它。2. 配置 VSCode 设置 确保 VSCode 配置正确,…...
【基于OpenEuler国产操作系统大数据实验环境搭建】
大数据实验环境搭建 一、实验简介1.1 实验内容1.2 环境及其资源规划 二、实验目的三、实验过程3.1 安装虚拟机软件及操作系统3.2 创建安装目录(在主节点上操作)3.2 安装JDK及基本设置(所有节点都需要操作)3.3 安装Hadoop3.4 安装Z…...
期末软件经济学
文章目录 前言复习策略复习名词解释简答题第一章 ppt后记 前言 最近白天都在忙正事,晚上锻炼一下,然后处理一些杂事,现在是晚上十点多,还有一些时间复习一下期末考试。复习到十一点。 复习策略 感觉比较简单,直接刷…...
滑动窗口算法专题
滑动窗口简介 滑动窗口就是利用单调性,配合同向双指针来优化暴力枚举的一种算法。 该算法主要有四个步骤 1. 先进进窗口 2. 判断条件,后续根据条件来判断是出窗口还是进窗口 3. 出窗口 4.更新结果,更新结果这个步骤是不确定的,…...
基于Java的世界时区自动计算及时间生成方法
目录 前言 一、zoneinfo简介 1、zoneinfo是什么 2、zoneinfo有什么 二、在Java中进行时区转换 1、Java与zoneInfo 2、Java展示zoneInfo实例 3、Java获取时区ID 三、Java通过经纬度获取时区 1、通过经度求解偏移 2、通过偏移量计算时间 3、统一的处理算法 四、总结 …...
Excel + Notepad + CMD 命令行批量修改文件名
注意:该方式为直接修改原文件的文件名,不会生成新文件 新建Excel文件 A列:固定为 renB列:原文件名称C列:修改后保存的名称B列、C列,需要带文件后缀,为txt文件就是.txt结尾,为png图片…...
OpenGL 几何着色器高级应用
几何着色器高级应用 概念回顾 几何着色器(Geometry Shader)是 OpenGL 管线中的可选着色器阶段,位于顶点着色器(Vertex Shader) 和光栅化阶段 之间。 其核心功能是基于输入的图元(如点、线或三角形),生成新的图元,或对输入的图元进行修改。 几何着色器的执行是以图元…...
【Unity基础】Unity 2D实现拖拽功能的10种方法
方法1. 基于 Update 循环的拖拽方法 (DragDrop2D) 代码概述 using System.Collections; using System.Collections.Generic; using UnityEngine;public class DragDrop2D : MonoBehaviour {bool isDraggable;bool isDragging;Collider2D objectCollider;void Start(){objectC…...
duxapp中兼容多端的 BoxShadow 阴影组件
由于RN 安卓端对阴影的支持不太完善,使用这个组件可以实现阴影效果 在RN端是使用 react-native-fast-shadow 实现的 示例 import { BoxShadow, Text } from /duxui<BoxShadow><Text>这是内容</Text> </BoxShadow>Props 继承自Taro的View…...
服务器---centos上安装docker并使用docker配置jenkins
要在 Docker 中安装 Jenkins 并进行管理,可以按照以下步骤操作: 1. 安装 Docker 首先,确保你的系统已经安装了 Docker。如果尚未安装,可以使用以下命令进行安装: 在 CentOS 上安装 Docker sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://…...
Linux系统操作03|chmod、vim
上文: Linux系统操作02|基本命令-CSDN博客 目录 六、chmod:给文件设置权限 1、字母法 2、数字法(用的最多) 七、vim:代码编写和文本编辑 1、启动和退出 1️⃣启动 2️⃣退出 2、vim基本操作 六、chmod&#x…...
数据库同步中间件DBSyncer安装配置及使用
1、介绍 DBSyncer(英[dbsɪŋkɜː],美[dbsɪŋkɜː 简称dbs)是一款开源的数据同步中间件,提供MySQL、Oracle、SqlServer、PostgreSQL、Elasticsearch(ES)、Kafka、File、SQL等同步场景。支持上传插件自定义同步转换业务…...
虚幻5描边轮廓材质
很多游戏内都有这种描边效果,挺实用也挺好看的,简单复刻一下 效果演示: Linethickness可以控制轮廓线条的粗细 这样连完,然后放到网格体细节的覆层材质上即可 可以自己更改粗细大小和颜色...
ISP帳戶會記錄什麼資訊?
許多用戶並不知道ISP會記錄有關線上活動的大量資訊。從流覽歷史記錄到數據使用情況,ISP經常收集和保留用戶數據,引發一系列隱私問題。 ISP 記錄哪些數據? ISP可以根據其隱私政策記錄各種類型的資訊。常見的記錄數據包括: 1.流覽…...
Facebook如何避免因IP变动而封号?实用指南
随着Facebook在个人社交与商业推广中的广泛应用,越来越多的用户面临因“IP变动”而被封号的问题。尤其是跨境电商、广告运营者和多账号管理用户,这种情况可能严重影响正常使用和业务发展。那么,如何避免因IP变动导致的封号问题?本…...
EXCEL数据清洗的几个功能总结备忘
目录 0 参考教材 1 用EXCEL进行数据清洗的几个功能 2 删除重复值: 3 找到缺失值等 4 大小写转换 5 类型转化 6 识别空格 0 参考教材 精通EXCEL数据统计与分析,中国,李宗璋用EXCEL学统计学,日EXCEL统计分析与决策&#x…...
web网页连接MQTT,显示数据与下发控制命令
web网页连接MQTT,显示数据与下发控制命令 零、前言 在完成一些设备作品后,常常会因为没有一个上位机用来实时检测数据和下发命令而苦恼,在上一篇文章中提到了怎么白嫖阿里云服务器,并且在上面搭建了属于自己的web网站。那么现在…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...



