Go defer(二):从汇编的角度理解延迟调用的实现

Go的延迟调用机制会在当前函数返回前执行传入的函数,它会经常被用于关闭文件描述符、关闭数据库连接以及解锁资源。之前的文章( Go defer(一):延迟调用的使用及其底层实现原理详解 )详细介绍了defer的使用以及其底层实现原理,本文则以Go 1.16版本以及AMD 64架构为实验环境,从汇编语言的角度去分析defer的实现原理,主要涉及defer参数传递方式、闭包、返回值修改、内存分配方式等内容。

1 使用特性

1.1 函数传参

在使用defer实现延迟调用的时候,需要特别注意的是注册函数的参数传参方式,如果直接使用的常规的函数传参,那么参数值在函数注册到defer链表上时就已经被确定了,这时候延迟的只是函数调用的时机,参数值早在一开始构建_defer结构体的时候就已经确定了(如果是引用语义的类型,则需要特别注意,这里仅以int类型作为参数进行分析)。以下面的sum函数为例,分析其汇编代码不难发现:

  • _defer结构体的内存布局会将注册函数所需的参数、返回值所需的内存空间紧挨着_defer结构体。这里需要特别注意下,在_defer结构体的内存布局中,前8个字节包含siz(4字节)、started(1字节)、heap(1字节)、openDefer(1字节)。关于Go结构体内存布局,感兴趣的可以参考下Go struct:结构体使用基础以及内存布局。
  • 按照函数传参的方式进行defer注册,编译器直接将sum函数所需要的参数值赋值到了_defer结构体存储func参数的栈空间(值拷贝)
  • 由于函数传参的方式是以值拷贝实现的,因此后续代码执行的变量自增操作不会对之前延迟注册函数所需的参数产生影响。(注意:Go的引用类型,slice、map、chan、interface,这些类型由于底层包含指针,虽然是值拷贝,但受指针影响,后续的修改可能会对延迟调用产生影响
func sum(a, b int) int {return a + b
}func main() {a, b := 1, 2// 通过函数传参的方式注册的延迟调用,在注册时已经将函数的形参值确定了defer sum(a, b) a++
}"".main STEXT size=197 args=0x0 locals=0x90 funcid=0x00x0000 00000 (main.go:20)       TEXT    "".main(SB), ABIInternal, $144-0...0x002f 00047 (main.go:22)       MOVQ    $1, "".a+32(SP) // 变量a0x0038 00056 (main.go:22)       MOVQ    $2, "".b+24(SP) // 变量b0x0041 00065 (main.go:23)       MOVL    $24, ""..autotmp_2+40(SP) // SP+40~SP+112的栈空间存储的是 _defer结构体0x0049 00073 (main.go:23)       LEAQ    "".sum·f(SB), AX0x0050 00080 (main.go:23)       MOVQ    AX, ""..autotmp_2+64(SP)0x0055 00085 (main.go:23)       MOVQ    "".a+32(SP), AX0x005a 00090 (main.go:23)       MOVQ    AX, ""..autotmp_2+112(SP)0x005f 00095 (main.go:23)       MOVQ    "".b+24(SP), AX0x0064 00100 (main.go:23)       MOVQ    AX, ""..autotmp_2+120(SP)0x0069 00105 (main.go:23)       LEAQ    ""..autotmp_2+40(SP), AX0x006e 00110 (main.go:23)       MOVQ    AX, (SP)0x0072 00114 (main.go:23)       PCDATA  $1, $00x0072 00114 (main.go:23)       CALL    runtime.deferprocStack(SB)0x0077 00119 (main.go:23)       TESTL   AX, AX // 测试返回值(AX寄存器),如果deferprocStack调用成功,return0()会将AX设置为00x0079 00121 (main.go:23)       JNE     161 // 如果返回值不为0,则跳转到161,不再执行main函数中的后续代码0x007b 00123 (main.go:23)       JMP     1250x007d 00125 (main.go:27)       MOVQ    "".a+32(SP), AX0x0082 00130 (main.go:27)       INCQ    AX // a++0x0085 00133 (main.go:27)       MOVQ    AX, "".a+32(SP)0x008a 00138 (main.go:28)       XCHGL   AX, AX0x008b 00139 (main.go:28)       CALL    runtime.deferreturn(SB) // 执行延迟调用0x0090 00144 (main.go:28)       MOVQ    136(SP), BP // 恢复调用者的BP0x0098 00152 (main.go:28)       ADDQ    $144, SP // 清理栈空间0x009f 00159 (main.go:28)       NOP0x00a0 00160 (main.go:28)       RET0x00a1 00161 (main.go:23)       XCHGL   AX, AX0x00a2 00162 (main.go:23)       CALL    runtime.deferreturn(SB)0x00a7 00167 (main.go:23)       MOVQ    136(SP), BP0x00af 00175 (main.go:23)       ADDQ    $144, SP0x00b6 00182 (main.go:23)       RET0x00b7 00183 (main.go:23)       NOP...

1.2 闭包

对于传统值拷贝的类型,想要在defer执行函数时,实时获取最新的参数值,则可以借助Go语言里面的闭包特性进行实现。闭包特性简单来说就是使用指针取代具体值进行传递,这样在后续需要使用该变量时,通过对地址的解引用来得到实时的值,从而实现变量最新值的获取。

闭包=函数地址 + 引用变量的地址

当函数引用外部作用域的变量时,我们称之为闭包。在底层实现上,闭包由函数地址和引用到的变量的地址组成,并存储在一个结构体里,在闭包被传递时,实际是该结构体的地址被传递。

type Closure struct {F func()()   // 函数地址 uintptri *int       // 引用变量的地址
}

还是以sum函数为例,其中两个参数a,b,采用闭包的方式构建匿名函数注册延迟调用,分析其汇编语言发现,主要的流程大致与函数传参的方式一致,区别在于:

  • 此时defer注册的是匿名函数func1,不再是sum函数,新函数仅需要两个变量,而没有返回值
  • func1函数所需的参数依旧保存在_defer结构体之后,只是编译器通过分析代码,对不同处境的变量做了不同的处理:
    • 对于后续不会再被改变的变量,编译器直接进行了值拷贝,例如此例中的变量b
    • 如果后续还会对变量进行修改,则编译器将其内存地址保存到了_defer结构体之后,例如此例中的变量a
  • 由于参数a保存的是其地址,那么在执行func1时,对其进行解引用拿到的值就是执行了自增之后的值,既实际sum函数执行时,两个参数的值a=2,b=2。
func sum(a, b int) int {return a + b
}func main() {a, b := 1, 2defer func() {sum(a, b)}()a++
}"".main STEXT size=170 args=0x0 locals=0x80 funcid=0x00x0000 00000 (main.go:20)       TEXT    "".main(SB), ABIInternal, $128-0...0x0021 00033 (main.go:22)       MOVQ    $1, "".a+24(SP)0x002a 00042 (main.go:22)       MOVQ    $2, "".b+16(SP)0x0033 00051 (main.go:23)       MOVL    $16, ""..autotmp_2+32(SP) // 此时注册的延迟调用函数为func匿名函数,仅有sum函数的两个变量,没有返回值0x003b 00059 (main.go:23)       LEAQ    "".main.func1·f(SB), AX0x0042 00066 (main.go:23)       MOVQ    AX, ""..autotmp_2+56(SP)0x0047 00071 (main.go:23)       LEAQ    "".a+24(SP), AX // 由于后面的代码还会对a进行修改,所以此处保存的是a的内存地址(闭包)0x004c 00076 (main.go:23)       MOVQ    AX, ""..autotmp_2+104(SP)0x0051 00081 (main.go:23)       MOVQ    "".b+16(SP), AX // b在后续不会再被修改,所以编译器进行了优化直接存储b的值0x0056 00086 (main.go:23)       MOVQ    AX, ""..autotmp_2+112(SP)0x005b 00091 (main.go:23)       LEAQ    ""..autotmp_2+32(SP), AX0x0060 00096 (main.go:23)       MOVQ    AX, (SP)0x0064 00100 (main.go:23)       PCDATA  $1, $00x0064 00100 (main.go:23)       CALL    runtime.deferprocStack(SB)0x0069 00105 (main.go:23)       TESTL   AX, AX // 判断deferprocStack函数是否正常执行return0(),如果没有正常执行直接跳转至143进行栈的清理工作0x006b 00107 (main.go:23)       JNE     1430x006d 00109 (main.go:23)       JMP     1110x006f 00111 (main.go:26)       MOVQ    "".a+24(SP), AX0x0074 00116 (main.go:26)       INCQ    AX0x0077 00119 (main.go:26)       MOVQ    AX, "".a+24(SP)0x007c 00124 (main.go:27)       XCHGL   AX, AX0x007d 00125 (main.go:27)       NOP0x0080 00128 (main.go:27)       CALL    runtime.deferreturn(SB)0x0085 00133 (main.go:27)       MOVQ    120(SP), BP0x008a 00138 (main.go:27)       SUBQ    $-128, SP0x008e 00142 (main.go:27)       RET0x008f 00143 (main.go:23)       XCHGL   AX, AX0x0090 00144 (main.go:23)       CALL    runtime.deferreturn(SB)0x0095 00149 (main.go:23)       MOVQ    120(SP), BP0x009a 00154 (main.go:23)       SUBQ    $-128, SP0x009e 00158 (main.go:23)       RET...

1.3 返回值修改

defer注册的函数是在主流程结束,函数返回之前被调用,那么如果在defer延迟调用的函数中对返回值进行修改,又会有怎么样的现象呢?从汇编的角度来看,return分为两步:先是对返回值进行赋值,最后函数结束时执行一个空的返回操作,而defer的执行时机则穿插在这两步之间。具体的执行流程如下:

  • 返回值 = xxx

  • 调用defer注册的函数

  • 空的return

根据上述的流程不难发现,可以在defer注册的延迟调用函数内部对返回值进行修改或赋值,下面从返回值重新赋值、直接修改返回值两种情况来分析下defer的延迟调用对返回值的影响。

1.3.1 返回值重新赋值

编写如下的代码,主要流程为定义一个变量t,通过闭包的形式对其在defer匿名函数中进行修改,并将该变量作为返回值进行返回。此函数有一个有名返回值r,既最后返回的值是一个内部的新变量,并不是直接定义的返回值变量r。通过汇编语言来分析下变量定义以及defer注册之后,其函数栈的具体情况:

  • 首先,Go的函数栈分布情况为:调用者函数栈帧会为被调用者预留参数、返回值所需的内存空间。如下图所示,main函数栈帧中会预留f函数的返回值空间。
  • 随后,进入f函数内部,有一个局部变量t,以及在栈上分配的_defer结构体,该_defer的延迟调用函数为匿名f.func1,所需参数为t(由于闭包,存储的是t的内存地址)

// 该函数返回值为5
func  f() (r int){t := 5defer func(){t = t + 5}()return t
}"".f STEXT size=155 args=0x8 locals=0x68 funcid=0x0...0x0021 00033 (main.go:4)        MOVQ    $0, "".r+112(SP) // 调用者的函数栈空间,用于存储f函数的返回值0x002a 00042 (main.go:5)        MOVQ    $5, "".t+8(SP)0x0033 00051 (main.go:6)        MOVL    $8, ""..autotmp_2+16(SP)0x003b 00059 (main.go:6)        LEAQ    "".f.func1·f(SB), AX0x0042 00066 (main.go:6)        MOVQ    AX, ""..autotmp_2+40(SP)0x0047 00071 (main.go:6)        LEAQ    "".t+8(SP), AX0x004c 00076 (main.go:6)        MOVQ    AX, ""..autotmp_2+88(SP)0x0051 00081 (main.go:6)        LEAQ    ""..autotmp_2+16(SP), AX0x0056 00086 (main.go:6)        MOVQ    AX, (SP)0x005a 00090 (main.go:6)        PCDATA  $1, $00x005a 00090 (main.go:6)        CALL    runtime.deferprocStack(SB)0x005f 00095 (main.go:6)        NOP

 

随后,代码开始执行return语句:

  • 将t值赋值给r作为返回值,从汇编语言的角度看就是将t的值拷贝到r的内存空间(调用者预留的空间内)。
  • 开始执行延迟调用函数,既f.func1函数,将t的变量值➕5,此处操作与变量r完全没有关系。
  • 最后执行一个空的返回操作。
        0x0060 00096 (main.go:6)        TESTL   AX, AX0x0062 00098 (main.go:6)        JNE     1290x0064 00100 (main.go:6)        JMP     1020x0066 00102 (main.go:9)        MOVQ    "".t+8(SP), AX 0x006b 00107 (main.go:9)        MOVQ    AX, "".r+112(SP) // 将t的值赋值给r,既将其存储到调用者的f函数返回值栈空间内0x0070 00112 (main.go:9)        XCHGL   AX, AX0x0071 00113 (main.go:9)        CALL    runtime.deferreturn(SB) // 执行延迟调用,将t的值➕50x0076 00118 (main.go:9)        MOVQ    96(SP), BP0x007b 00123 (main.go:9)        ADDQ    $104, SP0x007f 00127 (main.go:9)        NOP0x0080 00128 (main.go:9)        RET     // 相当于执行空的return操作..."".f.func1 STEXT nosplit size=21 args=0x8 locals=0x0 funcid=0x00x0000 00000 (main.go:6)        TEXT    "".f.func1(SB), NOSPLIT|ABIInternal, $0-80x0000 00000 (main.go:6)        FUNCDATA        $0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)0x0000 00000 (main.go:6)        FUNCDATA        $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)0x0000 00000 (main.go:7)        MOVQ    "".&t+8(SP), AX // AX = 捕获变量 t 的地址0x0005 00005 (main.go:7)        MOVQ    "".&t+8(SP), CX // CX = 捕获变量 t 的地址0x000a 00010 (main.go:7)        MOVQ    (CX), CX        // CX = *CX (解引用获取 t 的值)0x000d 00013 (main.go:7)        ADDQ    $5, CX          // CX = t + 50x0011 00017 (main.go:7)        MOVQ    CX, (AX)        // *AX = CX (将结果写回 t)0x0014 00020 (main.go:8)        RET

1.3.2 直接修改返回值

本例在之前的代码基础上进行修改,将返回值的名称修改为t,既函数内部不再重新定义一个新的变量,而是直接对返回值进行操作,此时编译器做出如下动作:

  • 首先,main函数栈帧中会预留f函数的返回值t,f函数对t的操作都直接会反应到返回值上。
  • 随后,在栈上分配的_defer结构体,该_defer的延迟调用函数为匿名f.func1,所需参数为t(由于闭包,存储的是t的内存地址,既返回值的地址)
  • 最后,deferreturn函数执行延迟调用时,执行匿名f.func1函数,会通过内存地址解引用找到t,对其进行➕5操作,这里的修改直接反映在f.func1函数的返回值上,因此最终f函数会返回10。

2 实现方式

2.1 堆分配

随着Go语言的不断优化发展,_defer结构体内存分配方式也不再仅有最原始的堆分配,新增了栈分配方式以及开发编码。虽然栈分配相比堆分配,性能占有,但其适用范围有效,而堆分配则适用于所用的情况,下面以循环内部注册defer触发编译时期无法确定具体defer数量为例,分析下此时函数栈、堆的具体情况:

  • 首先,由于_defer结构体所需的内存是在堆上进行分配的,那么函数栈帧中仅需存储调用deferproc所需的参数值、defer延迟调用函数所需的参数以及返回值空间。
  • 随后,调用deferproc函数,通过newdefer函数在堆上分配一块_defer结构体内存,将参数拷贝到对应位置,同时在defer结构体常规成员变量之后。紧接着拷贝注册的延迟调用函数所需参数以及返回值,并将该_defer结构体加入到Goroutine到_defer链表头部。
func sum(a, b int) int {return a + b
}func main() {for i := 0; i < 3; i++ {// 循环内的 defer 导致无法在编译时确定数量defer sum(i, i+1)}
}"".main STEXT size=170 args=0x0 locals=0x38 funcid=0x0...0x0021 00033 (main.go:9)        MOVQ    $0, "".i+40(SP) // 变量i初始化0x002a 00042 (main.go:9)        JMP     440x002c 00044 (main.go:9)        CMPQ    "".i+40(SP), $3 // i与3进行比较0x0032 00050 (main.go:9)        JLT     540x0034 00052 (main.go:9)        JMP     1430x0036 00054 (main.go:11)       MOVQ    "".i+40(SP), AX0x003b 00059 (main.go:11)       MOVQ    "".i+40(SP), CX0x0040 00064 (main.go:11)       MOVL    $24, (SP) // deferproc 函数的第一个参数siz0x0047 00071 (main.go:11)       LEAQ    "".sum·f(SB), DX0x004e 00078 (main.go:11)       MOVQ    DX, 8(SP) // deferproc 函数的第二个参数fn0x0053 00083 (main.go:11)       MOVQ    AX, 16(SP) // "".sum·f 函数的第一个参数0x0058 00088 (main.go:11)       LEAQ    1(CX), AX // AX = i+10x005c 00092 (main.go:11)       MOVQ    AX, 24(SP) // "".sum·f 函数的第二个参数0x0061 00097 (main.go:11)       PCDATA  $1, $00x0061 00097 (main.go:11)       CALL    runtime.deferproc(SB) // 调用 deferproc函数从堆上分配_defer所需的内存0x0066 00102 (main.go:11)       TESTL   AX, AX // return0()函数是否执行成功0x0068 00104 (main.go:11)       JNE     1250x006a 00106 (main.go:11)       JMP     1080x006c 00108 (main.go:9)        PCDATA  $1, $-10x006c 00108 (main.go:9)        JMP     1100x006e 00110 (main.go:9)        MOVQ    "".i+40(SP), AX0x0073 00115 (main.go:9)        INCQ    AX0x0076 00118 (main.go:9)        MOVQ    AX, "".i+40(SP)0x007b 00123 (main.go:9)        JMP     440x007d 00125 (main.go:11)       PCDATA  $1, $00x007d 00125 (main.go:11)       XCHGL   AX, AX0x007e 00126 (main.go:11)       NOP0x0080 00128 (main.go:11)       CALL    runtime.deferreturn(SB)0x0085 00133 (main.go:11)       MOVQ    48(SP), BP0x008a 00138 (main.go:11)       ADDQ    $56, SP0x008e 00142 (main.go:11)       RET...

2.2 栈分配

栈分配适用的条件没有堆分配那么多,仅适用于函数不逃逸且defer 数量确定的场景,由于此时_defer结构体是在函数栈帧上分配的,那么只需移动SP的值就可以完成_defer结构体内存的回收,执行效率很高。对比堆分配,栈分配只是将内存空间放在了栈上,_defer内存布局、延迟调用函数参数及返回值存储位置与堆分配完全一致。

func sum(a, b int) int {return a + b
}func main() {defer sum(1, 2) 
}"".main STEXT size=126 args=0x0 locals=0x80 funcid=0x0...0x001d 00029 (main.go:8)        MOVL    $24, ""..autotmp_0+24(SP)0x0025 00037 (main.go:8)        LEAQ    "".sum·f(SB), AX0x002c 00044 (main.go:8)        MOVQ    AX, ""..autotmp_0+48(SP)0x0031 00049 (main.go:8)        MOVQ    $1, ""..autotmp_0+96(SP)0x003a 00058 (main.go:8)        MOVQ    $2, ""..autotmp_0+104(SP)0x0043 00067 (main.go:8)        LEAQ    ""..autotmp_0+24(SP), AX0x0048 00072 (main.go:8)        MOVQ    AX, (SP)0x004c 00076 (main.go:8)        PCDATA  $1, $00x004c 00076 (main.go:8)        CALL    runtime.deferprocStack(SB)...

2.3 开放编码

使用go tool compile -S -l main.go 命令查看了下述简单代码在开放编码下的汇编语言,可以发现编译器没有再调用deferprocStack、deferproc去为每个defer生成一个defer结构体,而是直接编译成函数调用的方式,相当于把延迟调用改写成了在函数返回之前需要进行的正常函数调用。

func sum(a, b int) int {return a + b
}func main() {defer sum(1, 2)
}$  go tool compile -S -l main.go "".main STEXT size=140 args=0x0 locals=0x40 funcid=0x0...0x0029 00041 (main.go:11)       FUNCDATA        $4, "".main.opendefer(SB) // 标记使用了开放编码0x0029 00041 (main.go:11)       MOVB    $0, ""..autotmp_0+31(SP)0x002e 00046 (main.go:12)       LEAQ    "".sum·f(SB), AX0x0035 00053 (main.go:12)       MOVQ    AX, ""..autotmp_1+48(SP)0x003a 00058 (main.go:12)       MOVQ    $1, ""..autotmp_2+40(SP)0x0043 00067 (main.go:12)       MOVQ    $2, ""..autotmp_3+32(SP)0x004c 00076 (main.go:13)       MOVB    $0, ""..autotmp_0+31(SP)0x0051 00081 (main.go:13)       MOVQ    ""..autotmp_2+40(SP), AX0x0056 00086 (main.go:13)       MOVQ    ""..autotmp_3+32(SP), CX0x005b 00091 (main.go:13)       MOVQ    AX, (SP)0x005f 00095 (main.go:13)       MOVQ    CX, 8(SP)0x0064 00100 (main.go:13)       PCDATA  $1, $10x0064 00100 (main.go:13)       CALL    "".sum(SB) // 直接进行了函数调用0x0069 00105 (main.go:13)       MOVQ    56(SP), BP0x006e 00110 (main.go:13)       ADDQ    $64, SP0x0072 00114 (main.go:13)       RET0x0073 00115 (main.go:13)       CALL    runtime.deferreturn(SB)0x0078 00120 (main.go:13)       MOVQ    56(SP), BP0x007d 00125 (main.go:13)       ADDQ    $64, SP0x0081 00129 (main.go:13)       RET...

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

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

相关文章

Android 12系统源码_分屏模式(一)从最近任务触发分屏模式

前言 打开MainActivity&#xff0c;然后进入最近任务触发分屏&#xff0c;可以成功进入分屏模式。 本篇文章我们来具体梳理一下这个过程的源码调用流程。 一 launcher3阶段 1.1 源码 //packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/TaskView.java publi…

Flask 入门教程:用 Python 快速搭建你的第一个 Web 应用

文章目录前言一、什么是 Flask&#xff1f;&#x1f4cc; Flask 的优势1. 轻量灵活2. 易于上手3. 可扩展性强4. 自由度高5. 社区活跃&#xff0c;资料丰富Flask 主要用来做什么&#xff1f;二、Flask快速入门1.创建一个Flask项目2.开启debug&#xff0c;修改host&#xff0c;端…

实习第一个小需求样式问题总结

Vue2 vxe-table Element UI 表头下拉详情实现总结一、核心功能实现表头下拉按钮交互初始尝试 expand-change 事件无法满足需求&#xff0c;改用 vxe-table 的 toggle-row-expand 事件&#xff1a;<vxe-table toggle-row-expand"handleExpandChange"><temp…

Linux中LVM逻辑卷扩容

在Linux系统中对根目录所在的LVM逻辑卷进行扩容&#xff0c;需要依次完成 物理卷扩容 ➔ 卷组扩容 ➔ 逻辑卷扩容 ➔ 文件系统扩容 四个步骤。以下是详细操作流程&#xff1a;一、确认当前磁盘和LVM状态# 1. 查看磁盘空间使用情况 df -h /# 2. 查看块设备及LVM层级关系 lsblk# …

微软365 PDF导出功能存在本地文件包含漏洞,可泄露敏感服务器数据

微软365的"导出为PDF"功能近期被发现存在严重的本地文件包含(Local File Inclusion, LFI)漏洞&#xff0c;攻击者可利用该漏洞获取服务器端的敏感数据&#xff0c;包括配置文件、数据库凭证和应用程序源代码。该漏洞由安全研究员Gianluca Baldi发现并报告给微软&…

台球 PCOL:极致物理还原的网页斯诺克引擎(附源码深度解析)

> 无需下载,打开浏览器即可体验专业级斯诺克!本文将揭秘网页版台球游戏的物理引擎与渲染核心技术 在游戏开发领域,台球物理模拟一直被视为**刚体动力学皇冠上的明珠**。今天我们要解析的**台球 PCOL**(Pure Canvas Online Billiards)正是一款突破性的网页版斯诺克游戏…

springboot-2.3.3.RELEASE升级2.7.16,swagger2.9.2升级3.0.0过程

一、pom文件版本修改<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.16</version><relativePath/> </parent>如果用到了“spring-boot-starter…

Python-正则表达式-信息提取-滑动窗口-数据分发-文件加载及分析器-浏览器分析-学习笔记

序 欠4前年的一份笔记 &#xff0c;献给今后的自己。 正则表达式 概述 正则表达式&#xff0c;Regular Expression&#xff0c;缩写为regex、regexp、RE等。 正则表达式是文本处理极为重要的技术&#xff0c;用它可以对字符串按照某种规则进行检索、替换。 1970年代&…

一文入门神经网络:神经网络概念初识

神经网络的世界远比你想象得更丰富多元。从基础架构到前沿融合模型&#xff0c;我为你梳理了当前最值得关注的神经网络类型&#xff0c;不仅包括那些“教科书级”的经典模型&#xff0c;也覆盖了正在改变行业格局的新兴架构。以下是系统分类与核心特点总结&#xff1a;一、基础…

线上事故处理记录

线上事故处理记录 一、MySQL 导致的服务器 CPU 飙升 有一天&#xff0c;突然收到了服务器 CPU 飙升的告警信息&#xff0c;打开普罗米修斯查看 CPU 的使用情况&#xff0c;发现 CPU 确实飙升了&#xff0c;下面开始去进行问题定位了。 1. 首先连接到对应的服务器&#xff0c;然…

ParaCAD 笔记 png 图纸标注数据集

ParaCAD-Dataset git lfs install git clone https://www.modelscope.cn/datasets/yuwenbonnie/ParaCAD-Dataset.git https://github.com/ParaCAD/ 不止100g 下个最小的 没有三视图

C#使用Semantic Kernel实现Embedding功能

1、背景 C#开发中&#xff0c;可以通过Semantic Kernel实现本地模型的调用和实现。 本地的Ollama的版本如下&#xff1a;安装的Package如下&#xff1a;2、代码实现 // See https://aka.ms/new-console-template for more information using Microsoft.Extensions.AI; using Mi…

转转APP逆向

APP版本 11.15.0 接口分析 # URL https://app.zhuanzhuan.com/zz/transfer/search# header cookie xxx x-zz-monitoring-metrics feMetricAntiCheatLevelV1 zztk user-agent Zhuan/11.15.0 (11015000) Dalvik/2.1.0 (Linux; U; Android 10; Pixel 3 Build/QQ3A.200805.001) z…

注解与反射的完美配合:Java中的声明式编程实践

注解与反射的完美配合&#xff1a;Java中的声明式编程实践 目录 引言 核心概念 工作机制 实战示例 传统方式的痛点 注解反射的优势 实际应用场景 最佳实践 总结 引言 在现代Java开发中&#xff0c;我们经常看到这样的代码&#xff1a; Range(min 1, max 50)priva…

开源入侵防御系统——CrowdSec

1、简介 CrowdSec 是一款现代化、开源、基于行为的入侵防御系统&#xff08;IDS/IPS&#xff09;&#xff0c;专为保护服务器、服务、容器、云原生应用而设计。它通过分析日志检测可疑行为&#xff0c;并可基于社区协作共享恶意 IP 黑名单&#xff0c;从而实现分布式防御。 其…

imx6ull-裸机学习实验13——串口格式化函数移植实验

目录 前言 格式化函数 实验程序编写 stdio文件夹 main.c Makefile修改 编译下载 前言 在学习实验12&#xff1a;imx6ull串口通信实验&#xff0c;我们实现了 UART1 基本的数据收发功能&#xff0c;虽然可以用来调试程序&#xff0c;但是功能太单一了&#xff0c;只能输出…

CCF-GESP 等级考试 2025年6月认证C++三级真题解析

1 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09;第1题 8位二进制原码能表示的最小整数是&#xff1a;&#xff08; &#xff09;A. -127 B. -128 C. -255 …

【网络安全】服务间身份认证与授权模式

未经许可,不得转载。 文章目录 问题背景用户到服务的身份认证与授权系统对系统的通信服务与服务之间的通信需求分析Basic Auth(基本身份认证)优点缺点mTLS 证书认证优点缺点OAuth 2.0优点缺点JWS(JSON Web Signature)优点缺点结合 Open Policy Agent 的 JWS 方案优点缺点结…

【EGSR2025】材质+扩散模型+神经网络相关论文整理随笔(四)

An evaluation of SVBRDF Prediction from Generative Image Models for Appearance Modeling of 3D Scenes输入3D场景的几何和一张参考图像&#xff0c;通过扩散模型和SVBRDF预测器获取多视角的材质maps&#xff0c;这些maps最终合并成场景的纹理地图集&#xff0c;并支持在任…

Grid网格布局完整功能介绍和示例演示

CSS Grid布局是一种强大的二维布局系统&#xff0c;可以将页面划分为行和列&#xff0c;精确控制元素的位置和大小。以下是其完整功能介绍和示例演示&#xff1a; 基本概念 网格容器&#xff08;Grid Container&#xff09;&#xff1a;应用display: grid的元素。网格项&#x…