模型部署 - onnx的导出和分析 - onnx 的架构和 onnx helper 的使用 - 学习记录

onnx 的架构和 onnx helper 的使用

  • 简介
  • 一、onnx 的架构
  • 二、onnx 实践
    • 2.1、 create - linear.onnx
      • 2.1.1、要点一:创建节点
      • 2.1.2、要点二:创建张量
      • 2.1.3、要点三:创建图
    • 2.2、 create - onnx.convnet
    • 2.3、使用 onnx helper 导出的基本流程总结
  • 三、parse onnx
    • 3.1、案例一
    • 3.2、案例二(带有权重的)

简介

首先来了解一下 学习 onnx 架构和 onnx helper 的使用的目的。

一般模型部署的流程是 前处理+模型推理+后处理,然后c++ 上进行推理。

  • 前处理(与tensorRT无关)
  • 模型推理(生成的engine交给tensorRT去做前向传播)
  • 后处理(与tensorRT无关)

模型推理部分是指利用 tensorRT 的 onnx 解析器编译生成 engine (即转换为tensorRT能看懂的模型)。

1、有些时候我们应该把后处理部分在onnx模型中实现,降低后处理复杂度。
比如说yolov5的后处理中,要借助anchor要做一些乘加的操作,如果我们单独分开在后处理中去做的话,你就会发现你既要准备一个模型,还得专门储存这个模型的anchor的信息,这样代码的复杂度就很高,后处理的逻辑就会非常麻烦。所以把后处理的逻辑尽量得放在模型里面,使得它的tensor很简单通过decode就能实现。然后自己做的后处理性能可能还不够高,如果放到onnx里,tensorRT顺便还能帮你加速一下。

很多时候我们onnx已经导出来了,如果我还想去实现onnx后处理的增加,该怎么做呢? 有两种做法,一种是直接用onnx这个包去操作onnx文件,去增加一些节点是没有问题的,但这个难度系数比较高。第二种做法是可以用pytorch去实现后处理逻辑的代码,把这个后处理专门导出一个onnx,然后再把这个onnx合并到原来的onnx上,这也是实际上我们针对复杂任务专门定制的一个做法。

2、还有些时候我们无法直接用pytorch的export_onnx函数导出onnx,这个时候就要自己构建一个onnx 了。
比如 bevfusion的 spconv 部分,利用 onnx.helper() 自己构建一个onnx,然后再转 tensorrt

这些场景都需要自己理解、解析和构建 onnx。

一、onnx 的架构

首先我们来理解一下 onnx 的架构:ONNX是一种神经网络的格式,采用Protobuf (Protocal Buffer。是Google提出来的一套表示和序列化数据的机制) 二进制形式进行序列化模型。Protobuf会根据用于定义的数据结构来进行序列化存储。我们可以根据官方提供的数据结构信息,去修改或者创建onnx。onnx的各类proto的定义需要看官方文档 (https://github.com/onnx/onnx/tree/main)。这里面的onnx/onnx.in.proto定义了所有onnx的Proto 。

大概 总结 onnx 中的组织结构 如下:

  • ModelProto (描述的是整个模型的信息)
    — GraphProto (描述的是整个网络的信息)
    ------ NodeProto (描述的是各个计算节点,比如conv, linear)
    ------ TensorProto (描述的是tensor的信息,主要包括权重)
    ------ ValueInfoProto (描述的是input/output信息)
    ------ AttributeProto (描述的是node节点的各种属性信息)

下面我们根据总结的组织结构信息,来实践创建几个 onnx 。

二、onnx 实践

2.1、 create - linear.onnx

总体程序如下:

import onnx
from onnx import helper
from onnx import TensorProtodef create_onnx():# 创建ValueProtoa = helper.make_tensor_value_info('a', TensorProto.FLOAT, [10, 10])x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 10])b = helper.make_tensor_value_info('b', TensorProto.FLOAT, [10, 10])y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10, 10])# 创建NodeProtomul = helper.make_node('Mul', ['a', 'x'], 'c', "multiply")add = helper.make_node('Add', ['c', 'b'], 'y', "add")# 构建GraphProtograph = helper.make_graph([mul, add], 'sample-linear', [a, x, b], [y])# 构建ModelProtomodel = helper.make_model(graph)# 检查model是否有错误onnx.checker.check_model(model)# print(model)# 保存modelonnx.save(model, "sample-linear.onnx")return modelif __name__ == "__main__":model = create_onnx()

程序执行完会出现一个 sample-linear.onnx ,节点图如下。下面我们来看看程序里面的细节。
在这里插入图片描述)

2.1.1、要点一:创建节点

使用 helper.make_node 创建节点 (图中的黑色部分 'Mul','Add'

我们用下面两句话创建两个节点。

 # 创建NodeProtomul = helper.make_node('Mul', ['a', 'x'], 'c', "multiply")add = helper.make_node('Add', ['c', 'b'], 'y', "add")

函数对应参数解释:

在这里插入图片描述

op_type :The name of the operator to construct(要构造的运算符的名称)

这里填入的是onnx支持的算子的名称。这个地方是不可以乱写的,比如 不能将 ‘Mul’ 写成 ‘Mul2’,具体的参数查阅在 https://github.com/onnx/onnx/blob/main/docs/Operators.md

在这里插入图片描述

inputs :list of input names(节点输入名称),比如这里 Mul 的输入名字 是 ['a', 'x'] 两个
outputs : list of output names(节点输出名称)比如这里 Mul 的输出名字 是'c'一个
name : optional unique identifier for NodeProto(NodeProto的可选唯一标识符)

NodeProto 总结解释:

onnx中的 NodeProto,一般用来定义一个计算节点,比如conv, linear。
(input是repeated类型,意味着是数组)
(output是repeated类型,意味着是数组)
(attribute有一个自己的Proto)
(op_type需要严格根据onnx所提供的Operators写)

2.1.2、要点二:创建张量

helper.make_tensor, helper.make_value_info

一般用来定义网络的 input/output (会根据input/output的type来附加属性)

    a = helper.make_tensor_value_info('a', TensorProto.FLOAT, [10, 10])x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 10])b = helper.make_tensor_value_info('b', TensorProto.FLOAT, [10, 10])y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10, 10])

2.1.3、要点三:创建图

helper.make_graph

   # 构建GraphProtograph = helper.make_graph([mul, add], 'sample-linear', [a, x, b], [y])

nodes: list of NodeProto
name (string): graph name
inputs: list of ValueInfoProto
outputs: list of ValueInfoProto

GraphProto 总结解释:

一般用来定义一个网络。包括
• input/output
• initializer (在onnx中一般表示权重信息)
• node
(node是repeated,所以是数组)
(initializer是repeated,所以是数组)
(input/output是repeated,所以是数组)

2.2、 create - onnx.convnet

import numpy as np
import onnx
from onnx import numpy_helperdef create_initializer_tensor(name: str,tensor_array: np.ndarray,data_type: onnx.TensorProto = onnx.TensorProto.FLOAT
) -> onnx.TensorProto:initializer = onnx.helper.make_tensor(name      = name,data_type = data_type,dims      = tensor_array.shape,vals      = tensor_array.flatten().tolist())return initializerdef main():input_batch    = 1;input_channel  = 3;input_height   = 64;input_width    = 64;output_channel = 16;input_shape    = [input_batch, input_channel, input_height, input_width]output_shape   = [input_batch, output_channel, 1, 1]##########################创建input/output################################model_input_name  = "input0"model_output_name = "output0"input = onnx.helper.make_tensor_value_info(model_input_name,onnx.TensorProto.FLOAT,input_shape)output = onnx.helper.make_tensor_value_info(model_output_name, onnx.TensorProto.FLOAT, output_shape)##########################创建第一个conv节点##############################conv1_output_name = "conv2d_1.output"conv1_in_ch       = input_channelconv1_out_ch      = 32conv1_kernel      = 3conv1_pads        = 1# 创建conv节点的权重信息conv1_weight    = np.random.rand(conv1_out_ch, conv1_in_ch, conv1_kernel, conv1_kernel)conv1_bias      = np.random.rand(conv1_out_ch)conv1_weight_name = "conv2d_1.weight"conv1_weight_initializer = create_initializer_tensor(name         = conv1_weight_name,tensor_array = conv1_weight,data_type    = onnx.TensorProto.FLOAT)conv1_bias_name  = "conv2d_1.bias"conv1_bias_initializer = create_initializer_tensor(name         = conv1_bias_name,tensor_array = conv1_bias,data_type    = onnx.TensorProto.FLOAT)# 创建conv节点,注意conv节点的输入有3个: input, w, bconv1_node = onnx.helper.make_node(name         = "conv2d_1",op_type      = "Conv",inputs       = [model_input_name, conv1_weight_name,conv1_bias_name],outputs      = [conv1_output_name],kernel_shape = [conv1_kernel, conv1_kernel],pads         = [conv1_pads, conv1_pads, conv1_pads, conv1_pads],)##########################创建一个BatchNorm节点###########################bn1_output_name = "batchNorm1.output"# 为BN节点添加权重信息bn1_scale = np.random.rand(conv1_out_ch)bn1_bias  = np.random.rand(conv1_out_ch)bn1_mean  = np.random.rand(conv1_out_ch)bn1_var   = np.random.rand(conv1_out_ch)# 通过create_initializer_tensor创建权重,方法和创建conv节点一样bn1_scale_name = "batchNorm1.scale"bn1_bias_name  = "batchNorm1.bias"bn1_mean_name  = "batchNorm1.mean"bn1_var_name   = "batchNorm1.var"bn1_scale_initializer = create_initializer_tensor(name         = bn1_scale_name,tensor_array = bn1_scale,data_type    = onnx.TensorProto.FLOAT)bn1_bias_initializer = create_initializer_tensor(name         = bn1_bias_name,tensor_array = bn1_bias,data_type    = onnx.TensorProto.FLOAT)bn1_mean_initializer = create_initializer_tensor(name         = bn1_mean_name,tensor_array = bn1_mean,data_type    = onnx.TensorProto.FLOAT)bn1_var_initializer  = create_initializer_tensor(name         = bn1_var_name,tensor_array = bn1_var,data_type    = onnx.TensorProto.FLOAT)# 创建BN节点,注意BN节点的输入信息有5个: input, scale, bias, mean, varbn1_node = onnx.helper.make_node(name    = "batchNorm1",op_type = "BatchNormalization",inputs  = [conv1_output_name,bn1_scale_name,bn1_bias_name,bn1_mean_name,bn1_var_name],outputs=[bn1_output_name],)##########################创建一个ReLU节点###########################relu1_output_name = "relu1.output"# 创建ReLU节点,ReLU不需要权重,所以直接make_node就好了relu1_node = onnx.helper.make_node(name    = "relu1",op_type = "Relu",inputs  = [bn1_output_name],outputs = [relu1_output_name],)##########################创建一个AveragePool节点####################avg_pool1_output_name = "avg_pool1.output"# 创建AvgPool节点,AvgPool不需要权重,所以直接make_node就好了avg_pool1_node = onnx.helper.make_node(name    = "avg_pool1",op_type = "GlobalAveragePool",inputs  = [relu1_output_name],outputs = [avg_pool1_output_name],)##########################创建第二个conv节点############################### 创建conv节点的属性conv2_in_ch  = conv1_out_chconv2_out_ch = output_channelconv2_kernel = 1conv2_pads   = 0# 创建conv节点的权重信息conv2_weight    = np.random.rand(conv2_out_ch, conv2_in_ch, conv2_kernel, conv2_kernel)conv2_bias      = np.random.rand(conv2_out_ch)conv2_weight_name = "conv2d_2.weight"conv2_weight_initializer = create_initializer_tensor(name         = conv2_weight_name,tensor_array = conv2_weight,data_type    = onnx.TensorProto.FLOAT)conv2_bias_name  = "conv2d_2.bias"conv2_bias_initializer = create_initializer_tensor(name         = conv2_bias_name,tensor_array = conv2_bias,data_type    = onnx.TensorProto.FLOAT)# 创建conv节点,注意conv节点的输入有3个: input, w, bconv2_node = onnx.helper.make_node(name         = "conv2d_2",op_type      = "Conv",inputs       = [avg_pool1_output_name,conv2_weight_name,conv2_bias_name],outputs      = [model_output_name],kernel_shape = [conv2_kernel, conv2_kernel],pads         = [conv2_pads, conv2_pads, conv2_pads, conv2_pads],)##########################创建graph##############################graph = onnx.helper.make_graph(name    = "sample-convnet",inputs  = [input],outputs = [output],nodes   = [conv1_node, bn1_node, relu1_node, avg_pool1_node, conv2_node],initializer =[conv1_weight_initializer, conv1_bias_initializer,bn1_scale_initializer, bn1_bias_initializer,bn1_mean_initializer, bn1_var_initializer,conv2_weight_initializer, conv2_bias_initializer],)##########################创建model##############################model = onnx.helper.make_model(graph, producer_name="onnx-sample")model.opset_import[0].version = 12##########################验证&保存model##############################model = onnx.shape_inference.infer_shapes(model)onnx.checker.check_model(model)print("Congratulations!! Succeed in creating {}.onnx".format(graph.name))onnx.save(model, "sample-convnet.onnx")# 使用onnx.helper创建一个最基本的ConvNet
#         input (ch=3, h=64, w=64)
#           |
#          Conv (in_ch=3, out_ch=32, kernel=3, pads=1)
#           |
#        BatchNorm
#           |
#          ReLU
#           |
#         AvgPool
#           |
#          Conv (in_ch=32, out_ch=10, kernel=1, pads=0)
#           |
#         output (ch=10, h=1, w=1)if __name__ == "__main__":main()

与案例1不同的地方在


def create_initializer_tensor(name: str,tensor_array: np.ndarray,data_type: onnx.TensorProto = onnx.TensorProto.FLOAT
) -> onnx.TensorProto:initializer = onnx.helper.make_tensor(name      = name,data_type = data_type,dims      = tensor_array.shape,vals      = tensor_array.flatten().tolist())return initializer

这里使用了 onnx.helper.make_tensor()

2.3、使用 onnx helper 导出的基本流程总结

  1. helper.make_node
  2. helper.make_tensor
  3. helper.make_value_info
  4. helper.make_graph
  5. helper.make_operatorsetid
  6. helper.make_model
  7. onnx.save_model

参考链接

三、parse onnx

下面的案例展示如何使用 python 把 onnx 打印出来

3.1、案例一

import onnxdef main(): model = onnx.load("sample-linear.onnx")onnx.checker.check_model(model)graph        = model.graphnodes        = graph.nodeinputs       = graph.inputoutputs      = graph.outputprint("\n**************parse input/output*****************")for input in inputs:input_shape = []for d in input.type.tensor_type.shape.dim:if d.dim_value == 0:input_shape.append(None)else:input_shape.append(d.dim_value)print("Input info: \\n\tname:      {} \\n\tdata Type: {} \\n\tshape:     {}".format(input.name, input.type.tensor_type.elem_type, input_shape))for output in outputs:output_shape = []for d in output.type.tensor_type.shape.dim:if d.dim_value == 0:output_shape.append(None)else:output_shape.append(d.dim_value)print("Output info: \\n\tname:      {} \\n\tdata Type: {} \\n\tshape:     {}".format(input.name, output.type.tensor_type.elem_type, input_shape))print("\n**************parse node************************")for node in nodes:print("node info: \\n\tname:      {} \\n\top_type:   {} \\n\tinputs:    {} \\n\toutputs:   {}".format(node.name, node.op_type, node.input, node.output))if __name__ == "__main__":main()

3.2、案例二(带有权重的)

这里有两个 py 文件

parser.py

import onnx
import numpy as np# 注意,因为weight是以字节的形式存储的,所以要想读,需要转变为float类型
def read_weight(initializer: onnx.TensorProto):shape = initializer.dimsdata  = np.frombuffer(initializer.raw_data, dtype=np.float32).reshape(shape)print("\n**************parse weight data******************")print("initializer info: \\n\tname:      {} \\n\tdata:    \n{}".format(initializer.name, data))def parse_onnx(model: onnx.ModelProto):graph        = model.graphinitializers = graph.initializernodes        = graph.nodeinputs       = graph.inputoutputs      = graph.outputprint("\n**************parse input/output*****************")for input in inputs:input_shape = []for d in input.type.tensor_type.shape.dim:if d.dim_value == 0:input_shape.append(None)else:input_shape.append(d.dim_value)print("Input info: \\n\tname:      {} \\n\tdata Type: {} \\n\tshape:     {}".format(input.name, input.type.tensor_type.elem_type, input_shape))for output in outputs:output_shape = []for d in output.type.tensor_type.shape.dim:if d.dim_value == 0:output_shape.append(None)else:output_shape.append(d.dim_value)print("Output info: \\n\tname:      {} \\n\tdata Type: {} \\n\tshape:     {}".format(input.name, output.type.tensor_type.elem_type, input_shape))print("\n**************parse node************************")for node in nodes:print("node info: \\n\tname:      {} \\n\top_type:   {} \\n\tinputs:    {} \\n\toutputs:   {}".format(node.name, node.op_type, node.input, node.output))print("\n**************parse initializer*****************")for initializer in initializers:print("initializer info: \\n\tname:      {} \\n\tdata_type: {} \\n\tshape:     {}".format(initializer.name, initializer.data_type, initializer.dims))

parse_onnx_cbr.py

import torch
import torch.nn as nn
import torch.onnx
import onnx
from parser import parse_onnx
from parser import read_weightclass Model(torch.nn.Module):def __init__(self):super().__init__()self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3)self.bn1   = nn.BatchNorm2d(num_features=16)self.act1  = nn.LeakyReLU()def forward(self, x):x = self.conv1(x)x = self.bn1(x)x = self.act1(x)return xdef export_norm_onnx():input   = torch.rand(1, 3, 5, 5)model   = Model()model.eval()file    = "sample-cbr.onnx"torch.onnx.export(model         = model, args          = (input,),f             = file,input_names   = ["input0"],output_names  = ["output0"],opset_version = 15)print("Finished normal onnx export")def main():export_norm_onnx()model = onnx.load_model("sample-cbr.onnx")parse_onnx(model)initializers = model.graph.initializerfor item in initializers:read_weight(item)if __name__ == "__main__":main()

sample-cbr.onnx 模型下载地址

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

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

相关文章

理解数据库习题

1.选择 (1)现实世界中客观存在并能相互区别的事物称为( )。 A.实体 B.实体集 C字段 D 记录 (2)下列实体类型的联系中,属于一对一联系的是( )A.教研室对教师的所属联系 …

[BT]BUUCTF刷题第2天(3.20)

第2天(共5题) Web [ACTF2020 新生赛]Exec Payload:target127.0.0.1;cat /flag 分号;在许多shell中用作命令分隔符,意味着在执行完前一个命令(这里是设置target变量)后,接着执行cat /flag命令…

企业工商年报注册注销商标注册异常处理小程序开源版开发

企业工商年报注册注销商标注册异常处理小程序开源版开发 1、独立业务模型包括:企业工商年报、企业工商登记注册、企业注销登记、企业异常处理。 2、通用业务模型适合各种业务,比如:商标注册代理、财务会计服务、企业版权登记登。 当然&…

从深度伪造到恶意软件:网络安全迎来AI新挑战

如今,有越来越多的恶意行为者开始利用AI大语言模型开发能够绕过 YARA 规则的自我增强型恶意软件。 根据近日Recorded Future 发布的一份新报告:AI可以通过增强小型恶意软件变种的源代码来规避基于字符串的 YARA 规则,从而有效降低检测率。 …

这里是一本关于 DevOps 企业级 CI/CD 实战的书籍...

文章目录 📋 前言🎯 什么是 DevOps🎯 什么是 CI/CD🎯什么是 Jenkins🧩 Jenkins 简单案例 🎯 DevOps 企业级实战书籍推荐🔥 参与方式 📋 前言 企业级 CI/CD 实战是一个涉及到软件开发…

Linux CentOS 7.6安装Redis 6.2.6 详细保姆级教程

1、安装依赖 //检查是否有依赖 gcc -v //没有则安装 yum install -y gcc2、下载redis安装包 //进入home目录 cd /home //通过wget下载redis安装包 wget https://download.redis.io/releases/redis-6.2.6.tar.gz //解压安装包 tar -zxvf redis-6.2.6.tar.gz3、编译 //进入解压…

【Linux】如何使用git命令行与远程仓库建立连接(以Gitee为例)

目录 01.创建仓库 开源 初始化​编辑 设置模版 ​编辑 02.下载仓库到本地 03.提交贡献到远程仓库 01.创建仓库 首先在Gitee网站上登录你的账户,并创建一个新的仓库,输入仓库名称后就会自动生成仓库路径 开源 可以选择你的仓库是否开源。 开源仓…

企业如何选择一个开源「好」项目?

开源 三句半 oss-roast 需求明确是关键 风险考量要周全 开源虽好不白捡 别忘合规! 显然,开源已成为一股不可阻挡的洪流,企业拥抱开源,积极参与开源项目不仅是响应技术潮流的必然选择,更是实现自身技术创新、市场拓展等…

AI开源概览及工具使用

一、前言 随着ChatGPT热度的攀升,越来越多的公司也相继推出了自己的AI大模型,如文心一言、通义千问等。各大应用也开始内置AI玩法,如抖音的AI特效; 关联资源:代码 GitHub、相关论文、项目Demo、产品文档、Grok Ai、gr…

【算法】雪花算法生成分布式 ID

SueWakeup 个人中心:SueWakeup 系列专栏:学习Java框架 个性签名:人生乏味啊,我欲令之光怪陆离 本文封面由 凯楠📷 友情赞助播出! 目录 1. 什么是分布式 ID 2. 分布式 ID 基本要求 3. 数据库主键自增 4. UUID 5. S…

【高频SQL (进阶版)】1398.购买了产品A和产品B却没有购买产品C的顾客Plus

思路: 思路1:买了A,买了B,没有买C。 按人分组统计,A的数>0, B的数>0 ,C的数 0。 思路2:反过来查,用户id。在产品表里,产品名为A,为B的用户列表里,但是不在产品…

ab (Apache benchmark) - 压力/性能测试工具

Apache benchmark(ab) 安装window安装使用方法 - bin目录运行使用方法 - 任意目录运行 linux安装 基本命令介绍常用参数:输出结果分析: ab的man手册 安装 window安装 官网下载链接:https://www.apachehaus.com/cgi-bin/download…

c++ 指针大小

C的一个指针占内存几个字节? 结论: 取决于是64位编译模式还是32位编译模式(注意,和机器位数没有直接关系) 在64位编译模式下,指针的占用内存大小是8字节在32位编译模式下,指针占用内存大小是4字…

分布式之SleuthZipkin

Sleuth&Zipkin 学习当前课程,比必须要先掌握SpringCloud的基本应用(Nacos,Feign调用) 对Docker有一定的了解,知道docker-compose.yml如何启动一个容器 RabbitMQ,Elasticsearch有一定了解。 而且学习…

[C++]20:unorderedset和unorderedmap结构和封装。

unorderedset和unorderedmap结构和封装 一.哈希表&#xff1a;1.直接定址法&#xff1a;2.闭散列的开放定址法&#xff1a;1.基本结构&#xff1a;2.insert3.find4.erase5.补充&#xff1a;6.pair<k,v> k的数据类型&#xff1a; 3.开散列的拉链法/哈希桶&#xff1a;1.基…

mabatis 下

mybatis 原生的API&注解的方式MyBatis-原生的API调用快速入门需求快速入门代码实现 MyBatis-注解的方式操作快速入门需求快速入门代码实现注意事项和说明 mybatis-config.xml配置文件详解说明properties属性settings全局参数定义typeAliases别名处理器typeHandlers类型处理…

几个特殊的控件

目录 一、3个button 1、button 2、linkbutton 3、ImageButton Enabled属性 二、Image控件 1、使用原因 2、使用方式 法一&#xff1a;指明路径 法二&#xff1a;同一目录 3、使用实例 &#xff08;1&#xff09;要求 &#xff08;2&#xff09;操作 三、Typelink和…

对https://registry.npm.taobao.org/tyarn的请求失败,原因:证书过期

今天安装tyarn时&#xff0c;报错如下&#xff1a; request to https://registry.npm.taobao.org/tyarn failed, reason: certificate has expired 原来淘宝镜像过期了&#xff0c;需要重新搞一下 记录一下解决过程&#xff1a; 1.查看当前npm配置 npm config list 2.清空…

JAVAEE多线程——锁

文章目录 什么是锁为什么需要锁如何加锁synchorized 的使用synchronized 修饰方法synchronized 修饰代码块 死锁问题那种场景会造成死锁死锁的本质由于内部存在无限循环导致的死锁 死锁的第二种情况哲学家吃饭模型造成死锁的必要条件 什么是锁 首先我们来解释一下什么是锁呢&a…

SpringBoot整合Xxl-Job

一、下载Xxl-Job源代码并导入本地并运行 Github地址:GitHub - xuxueli/xxl-job: A distributed task scheduling framework.&#xff08;分布式任务调度平台XXL-JOB&#xff09; 中文文档地址:分布式任务调度平台XXL-JOB 1.使用Idea或Eclipse导入 2.执行sql脚本(红色标记…