当前位置: 首页 > news >正文

Go Runtime功能初探


以下内容,是对 运行时 runtime的神奇用法[1] 的学习与记录


目录:

  • 1.获取GOROOT环境变量
  • 2.获取GO的版本号
  • 3.获取本机CPU个数
  • 4.设置最大可同时执行的最大CPU数
  • 5.设置cup profile 记录的速录
  • 6.查看cup profile 下一次堆栈跟踪数据
  • 7.立即执行一次垃圾回收
  • 8.给变量绑定方法,当垃圾回收的时候进行监听
  • 9.查看内存申请和分配统计信息
  • 10.查看程序正在使用的字节数
  • 11.查看程序正在使用的对象数
  • 12.获取调用堆栈列表
  • 13.获取内存profile记录历史
  • 14.执行一个断点
  • 15.获取程序调用go协程的栈踪迹历史
  • 16.获取当前函数或者上层函数的标识号、文件名、调用方法在当前文件中的行号
  • 17.获取与当前堆栈记录相关链的调用栈踪迹
  • 18.获取一个标识调用栈标识符pc对应的调用栈
  • 19.获取调用栈所调用的函数的名字
  • 20.获取调用栈所调用的函数的所在的源文件名和行号
  • 21.获取该调用栈的调用栈标识符
  • 22.获取当前进程执行的cgo调用次数
  • 23.获取当前存在的go协程数
  • 24.终止掉当前的go协程
  • 25.让其他go协程优先执行,等其他协程执行完后,在执行当前的协程
  • 26.获取活跃的go协程的堆栈profile以及记录个数
  • 27.将调用的go协程绑定到当前所在的操作系统线程,其它go协程不能进入该线程
  • 28.解除go协程与操作系统线程的绑定关系
  • 29.获取线程创建profile中的记录个数
  • 30.控制阻塞profile记录go协程阻塞事件的采样率
  • 31.返回当前阻塞profile中的记录个数



1. GOROOT()获取GOROOT环境变量


GOROOT() 返回Go的根目录。如果存在GOROOT环境变量,返回该变量的值;否则,返回创建Go时的根目录

package main

import (
 "fmt"
 "runtime"
)

func main() {
 fmt.Println(runtime.GOROOT()) // /Users/fliter/.g/go
}



2. Version() 获取GO的版本号


Version() 返回Go的版本字符串。要么是提交的hash和创建时的日期;要么是发行标签如"go1.20"

package main

import (
 "fmt"
 "runtime"
)

func main() {
 fmt.Println(runtime.Version()) // go1.19
}



3. NumCPU() 获取本机CPU个数


NumCPU返回本地机器的逻辑CPU个数

package main

import (
 "fmt"
 "runtime"
)

func main() {
 fmt.Println(runtime.NumCPU()) // 8

}




4. GOMAXPROCS() 设置最大可同时执行的最大CPU数


GOMAXPROCS() 设置可同时执行的最大CPU数,并返回先前的设置。 若 n < 1,则不会更改当前设置。

本地机器的逻辑CPU数可通过 NumCPU 查询。该函数在调度程序优化后会去掉?(啥时候..)

package main

import (
 "fmt"
 "runtime"
 "time"
)

func main() {

 runtime.GOMAXPROCS(1)
 startTime := time.Now()
 var s1 chan int64 = make(chan int64)
 var s2 chan int64 = make(chan int64)
 var s3 chan int64 = make(chan int64)
 var s4 chan int64 = make(chan int64)
 go calc(s1)
 go calc(s2)
 go calc(s3)
 go calc(s4)
 <-s1
 <-s2
 <-s3
 <-s4
 endTime := time.Now()
 fmt.Println(endTime.Sub(startTime)) // 第一行注释掉 耗时: 386.954625ms; 取消注释 耗时: 1.34715s

}
func calc(s chan int64) {
 var count int64 = 0
 for i := 0; i < 1000000000; i++ {
  count += int64(i)
 }
 s <- count
}



5. SetCPUProfileRate() 设置cup profile 记录的速录


SetCPUProfileRate() 设置CPU profile记录的速率为平均每秒hz次。如果hz<=0,SetCPUProfileRate会关闭profile的记录。如果记录器在执行,该速率必须在关闭之后才能修改

绝大多数使用者应使用runtime/pprof包或testing包的-test.cpuprofile选项而非直接使用SetCPUProfileRate




6. CPUProfile 查看cup profile 下一次堆栈跟踪数据


func CPUProfile() []byte

已废弃




7. GC() 立即执行一次垃圾回收


Go三种触发GC的方式之一 (另外两种为2分钟固定一次 && 达到阈值时触发)

package main

import (
 "runtime"
 "time"
)

type Student struct {
 name string
}

func main() {
 var i *Student = new(Student)
 runtime.SetFinalizer(i, func(i interface{}) {
  println("垃圾回收了"// 垃圾回收了
 })
 runtime.GC() // 如果将这行注释,则上面不会输出
 time.Sleep(time.Second)
}



8. SetFinalizer() 给变量绑定方法,当垃圾回收的时候进行监听


func SetFinalizer(x, f interface{})

注意x必须是指针类型,f 函数的参数一定要和x保持一致,或者写interface{},不然程序会报错

代码同上例




9. ReadMemStats() 查看内存申请和分配统计信息


可以获得如下信息:

type MemStats struct {
    // 一般统计
    Alloc      uint64 // 已申请且仍在使用的字节数
    TotalAlloc uint64 // 已申请的总字节数(已释放的部分也算在内)
    Sys        uint64 // 从系统中获取的字节数(下面XxxSys之和)
    Lookups    uint64 // 指针查找的次数
    Mallocs    uint64 // 申请内存的次数
    Frees      uint64 // 释放内存的次数
    // 主分配堆统计
    HeapAlloc    uint64 // 已申请且仍在使用的字节数
    HeapSys      uint64 // 从系统中获取的字节数
    HeapIdle     uint64 // 闲置span中的字节数
    HeapInuse    uint64 // 非闲置span中的字节数
    HeapReleased uint64 // 释放到系统的字节数
    HeapObjects  uint64 // 已分配对象的总个数
    // L低层次、大小固定的结构体分配器统计,Inuse为正在使用的字节数,Sys为从系统获取的字节数
    StackInuse  uint64 // 引导程序的堆栈
    StackSys    uint64
    MSpanInuse  uint64 // mspan结构体
    MSpanSys    uint64
    MCacheInuse uint64 // mcache结构体
    MCacheSys   uint64
    BuckHashSys uint64 // profile桶散列表
    GCSys       uint64 // GC元数据
    OtherSys    uint64 // 其他系统申请
    // 垃圾收集器统计
    NextGC       uint64 // 会在HeapAlloc字段到达该值(字节数)时运行下次GC
    LastGC       uint64 // 上次运行的绝对时间(纳秒)
    PauseTotalNs uint64
    PauseNs      [256]uint64 // 近期GC暂停时间的循环缓冲,最近一次在[(NumGC+255)%256]
    NumGC        uint32
    EnableGC     bool
    DebugGC      bool
    // 每次申请的字节数的统计,61是C代码中的尺寸分级数
    BySize [61]struct {
        Size    uint32
        Mallocs uint64
        Frees   uint64
    }
}
package main

import (
 "fmt"
 "runtime"
 "time"
)

type Student2 struct {
 name string
}

func main() {
 var list = make([]*Student2, 0)
 for i := 0; i < 100000; i++ {
  var s *Student2 = new(Student2)
  list = append(list, s)
 }
 memStatus := runtime.MemStats{}
 runtime.ReadMemStats(&memStatus)
 fmt.Printf("申请的内存:%d\n", memStatus.Mallocs) // 申请的内存:100250

 fmt.Printf("释放的内存次数:%d\n", memStatus.Frees) // 释放的内存次数:45

 time.Sleep(time.Second)
}



10. InUseBytes() 查看程序正在使用的字节数


func (r *MemProfileRecord) InUseBytes() int64

InUseBytes 返回正在使用的字节数(AllocBytes – FreeBytes)




11. InUseObjects() 查看程序正在使用的对象数


func (r *MemProfileRecord) InUseObjects() int64

InUseObjects 返回正在使用的对象数(AllocObjects - FreeObjects)




12. Stack() 获取调用堆栈列表


func (r *MemProfileRecord) Stack() []uintptr

Stack返回关联至此记录的调用栈踪迹,即r.Stack0的前缀




13. MemProfile() 获取内存profile记录历史


func MemProfile(p []MemProfileRecord, inuseZero bool) (n int, ok bool)

MemProfile 返回当前内存profile中的记录数n

  • 若len(p)>=n,MemProfile会将此分析报告复制到p中并返回(n, true);

  • 若len(p)<n,MemProfile则不会更改p,而只返回(n, false)

如果inuseZero为true,该profile就会包含无效分配记录(其中r.AllocBytes>0,而r.AllocBytes==r.FreeBytes。这些内存都是被申请后又释放回运行时环境的)

大多数调用者应当使用runtime/pprof包或testing包的-test.memprofile标记,而非直接调用MemProfile




14. Breakpoint() 执行一个断点


runtime.Breakpoint()




15. Stack() 获取程序调用go协程的栈踪迹历史


func Stack(buf []byte, all bool) int

Stack 将调用其的go程的调用栈踪迹格式化后写入到buf中并返回写入的字节数

若all为true,函数会在写入当前go程的踪迹信息后,将其它所有go程的调用栈踪迹都格式化写入到buf中

package main

import (
 "fmt"
 "runtime"
 "time"
)

func main() {
 go showRecord()
 time.Sleep(time.Second)
 buf := make([]byte10000000000)
 runtime.Stack(buf, true)
 fmt.Println(string(buf))
}

func showRecord() {
 ticker := time.Tick(time.Second)
 for t := range ticker {
  fmt.Println(t)
 }
}

输出:

2023-04-19 17:25:26.386522 +0800 CST m=+1.001105543
2023-04-19 17:25:27.386892 +0800 CST m=+2.001489376
2023-04-19 17:25:28.386505 +0800 CST m=+3.001116043
2023-04-19 17:25:29.38553 +0800 CST m=+4.000154334
2023-04-19 17:25:30.385618 +0800 CST m=+5.000255584
2023-04-19 17:25:31.385817 +0800 CST m=+6.000468334
2023-04-19 17:25:32.385528 +0800 CST m=+7.000192918
2023-04-19 17:25:33.385646 +0800 CST m=+8.000323543
goroutine 1 [running]:
main.main()
        /Users/fliter/runtime-demo/15Stack.go:13 +0x68

goroutine 4 [chan receive]:
main.showRecord()
        /Users/fliter/runtime-demo/15Stack.go:19 +0xac
created by main.main
        /Users/fliter/runtime-demo/15Stack.go:10 +0x24



16. Caller() 获取当前函数或者上层函数的标识号、文件名、调用方法在当前文件中的行号


func Caller(skip int) (pc uintptr, file string, line int, ok bool)

package main

import (
 "fmt"
 "runtime"
)

func main() {
 pc, file, line, ok := runtime.Caller(0)
 fmt.Println(pc)   // 4336410771
 fmt.Println(file) // /Users/fliter/runtime-demo/16Caller.go
 fmt.Println(line) //9
 fmt.Println(ok)   //true
}

pc = 4336410771 不是main函数的标识,而是runtime.Caller 方法的标识,line = 13 标识它在main方法中的第13行被调用

//package main
//
//import (
// "fmt"
// "runtime"
//)
//
//func main() {
// pc, file, line, ok := runtime.Caller(0)
// fmt.Println(pc)   // 4336410771
// fmt.Println(file) // /Users/fliter/runtime-demo/16Caller.go
// fmt.Println(line) //9
// fmt.Println(ok)   //true
//}

package main

import (
 "fmt"
 "runtime"
)

func main() {
 pc, _, line, _ := runtime.Caller(1)
 fmt.Printf("main函数的pc:%d\n", pc)      // main函数的pc:4364609931
 fmt.Printf("main函数被调用的行数:%d\n", line) // main函数被调用的行数:250
 show()
}
func show() {
 pc, _, line, _ := runtime.Caller(1)
 fmt.Printf("show函数的pc:%d\n", pc)      // show函数的pc:4364974271
 fmt.Printf("show函数被调用的行数:%d\n", line) // show函数被调用的行数:27
 // 这个是main函数的栈
 pc, _, line, _ = runtime.Caller(2)
 fmt.Printf("show的上层函数的pc:%d\n", pc)      // show的上层函数的pc:4364609931
 fmt.Printf("show的上层函数被调用的行数:%d\n", line) // show的上层函数被调用的行数:250
 pc, _, _, _ = runtime.Caller(3)
 fmt.Println(pc) //4364778899
 pc, _, _, _ = runtime.Caller(4)
 fmt.Println(pc) // 0
}


golang获取调用者的方法名及所在行数[2]

runtime.Caller的性能问题[3]




17. Callers() 获取与当前堆栈记录相关链的调用栈踪迹


func Callers(skip int, pc []uintptr) int

会把当前go程调用栈上的调用栈标识符填入切片pc中,返回写入到pc中的项数。实参skip为开始在pc中记录之前所要跳过的栈帧数,0表示Callers自身的调用栈,1表示Callers所在的调用栈。返回写入p的项数

package main

import (
 "fmt"
 "runtime"
)

func main() {
 pcs := make([]uintptr10)
 i := runtime.Callers(1, pcs)
 fmt.Println(pcs[:i]) // [4311883569 4311525404 4311694372]
}

获得了三个pc 其中有一个是main方法自身的




18. FuncForPC() 获取一个标识调用栈标识符pc对应的调用栈


func FuncForPC(pc uintptr) *Func

package main

import (
 "runtime"
)

func main() {

 pcs := make([]uintptr10)
 i := runtime.Callers(1, pcs)
 for _, pc := range pcs[:i] {
  println(runtime.FuncForPC(pc))
 }
}

输出:

0x102f660f0
0x102f5c9f0
0x102f64840

用途见下




19. Name() 获取调用栈所调用的函数的名字


func (f *Func) string

package main

import (
 "runtime"
)

func main() {

 pcs := make([]uintptr10)
 i := runtime.Callers(1, pcs)
 for _, pc := range pcs[:i] {
  funcPC := runtime.FuncForPC(pc)
  println(funcPC.Name())
 }
}

输出:

main.main
runtime.main
runtime.goexit



20. FileLine() 获取调用栈所调用的函数的所在的源文件名和行号


func (f *Func) FileLine(pc uintptr) (file string, line int)

package main

import (
 "runtime"
)

func main() {
 pcs := make([]uintptr10)
 i := runtime.Callers(1, pcs)
 for _, pc := range pcs[:i] {
  funcPC := runtime.FuncForPC(pc)
  file, line := funcPC.FileLine(pc)
  println(funcPC.Name(), file, line)
 }
}

输出:

main.main /Users/fliter/runtime-demo/20FileLine.go 9
runtime.main /Users/fliter/.g/go/src/runtime/proc.go 259
runtime.goexit /Users/fliter/.g/go/src/runtime/asm_arm64.s 1166



21. Entry() 获取该调用栈的调用栈标识符


func (f *Func) Entry() uintptr

package main

import (
 "runtime"
)

func main() {
 pcs := make([]uintptr10)
 i := runtime.Callers(1, pcs)
 for _, pc := range pcs[:i] {
  funcPC := runtime.FuncForPC(pc)
  println(funcPC.Entry())
 }
}

输出:

4310699120
4310540672
4310690704



22. NumCgoCall() 获取当前进程执行的cgo调用次数


获取当前进程调用C方法的次数

func NumCgoCall() int64

package main

import (
 "runtime"
)

/*
#include <stdio.h>
*/

import "C"

func main() {
 println(runtime.NumCgoCall()) // 1
}

没有调用C的方法为什么是1呢?因为 import C 会调用C包中的init方法


package main

import (
 "runtime"
)

/*
   #include <stdio.h>
   // 自定义一个c语言的方法
   static void myPrint(const char* msg) {
     printf("myPrint: %s", msg);
   }
*/

import "C"

func main() {
 // 调用c方法
 C.myPrint(C.CString("Hello,C\n")) // myPrint: Hello,C
 println(runtime.NumCgoCall()) // 3
}



23. NumGoroutine() 获取当前存在的go协程数


func NumGoroutine() int

package main

import "runtime"

func main() {
 go print()
 print()
 println(runtime.NumGoroutine()) // 2
}
func print() {

}

当前程序有 2个go协程 一个是main.go主协程, 另外一个是 go print()




24. Goexit() 终止掉当前的go协程


func Goexit()

package main

import (
 "fmt"
 "runtime"
)

func main() {
 print()
 fmt.Println("继续执行")
}
func print() {
 fmt.Println("准备结束go协程")
 runtime.Goexit()
 defer fmt.Println("结束了")
}

输出:

准备结束go协程
fatal error: no goroutines (main called runtime.Goexit) - deadlock!
exit status 2

Goexit终止调用它的go协程,其他协程不受影响,Goexit会在终止该go协程前执行所有的defer函数,前提是defer必须在它前面定义,如下

package main

import (
 "fmt"
 "runtime"
)

func main() {
 print()
 fmt.Println("继续执行")
}
func print() {
 fmt.Println("准备结束go协程")
 defer fmt.Println("结束了--会输出出来")
 runtime.Goexit()
 //defer fmt.Println("结束了")
}

输出:

准备结束go协程
结束了--会输出出来
fatal error: no goroutines (main called runtime.Goexit) - deadlock!
exit status 2

如果在 main主协程调用该方法,会终止 主协程,但不会让main返回,因为main函数没有返回,

程序会继续执行其他go协程,当其他go协程执行完毕后,程序就会崩溃

package main

import (
 "fmt"
 "runtime"
 "time"
)

func main() {

 start := time.Now()
 go func() {
  time.Sleep(3e9)
  println("123")
 }()

 defer fmt.Println(time.Since(start))
 runtime.Goexit()

}

输出:

20.5µs
123
fatal error: no goroutines (main called runtime.Goexit) - deadlock!
exit status 2



25. Gosched() 让其他go协程优先执行,等其他协程执行完后,再执行当前的协程


func Gosched()

package main

import (
 "fmt"
)

func main() {
 go print25()
 fmt.Println("继续执行")
}
func print25() {
 fmt.Println("执行打印方法")
}

调用了go print方法,但是还未执行, main函数就执行完毕了(启一个协程也是需要时间的,这个时间比for循环,比程序继续执行要耗时多很多)

可以使用channel,waitgroup等,此处使用 runtime.Gosched()

package main

import (
 "fmt"
 "runtime"
)

func main() {
 go print25()
 runtime.Gosched()
 fmt.Println("继续执行")
}
func print25() {
 fmt.Println("执行打印方法")
}

输出:

执行打印方法
继续执行

Rust vs Go:常用语法对比(13)-将优先权让给其他线程[4]

Go用两个协程交替打印100以内的奇偶数[5]




26. GoroutineProfile() 获取活跃的go协程的堆栈profile以及记录个数


func GoroutineProfile(p []StackRecord) (n int, ok bool)

// GoroutineProfile returns n, the number of records in the active goroutine stack profile.
// If len(p) >= n, GoroutineProfile copies the profile into p and returns n, true.
// If len(p) < n, GoroutineProfile does not change p and returns n, false.
//
// Most clients should use the runtime/pprof package instead
// of calling GoroutineProfile directly.
func GoroutineProfile(p []StackRecord) (n int, ok bool) {

 return goroutineProfileWithLabels(p, nil)
}
package main

import (
 "fmt"
 "runtime"
)

func main() {

 for i := 0; i < 10; i++ {

  go func(k int) {
   fmt.Println(i)
  }(i)
 }

 fmt.Println("----------------")
 p := make([]runtime.StackRecord, 10000)

 fmt.Println(runtime.GoroutineProfile(p)) // 1 true
}

输出:

10
10
10
10
10
10
10
----------------
7
10
10
1 true

pprof里面使用了此func

Go 应用的性能优化[6]




27. LockOSThread() 将调用的go协程绑定到当前所在的操作系统线程,其它go协程不能进入该线程


func LockOSThread()

将调用的go程绑定到它当前所在的操作系统线程。除非调用的go程退出或调用UnlockOSThread,否则它将总是在该线程中执行,而其它go程不能进入该线程

package main

import (
 "fmt"
 "runtime"
 "time"
)

func main() {
 go calcSum1()
 go calcSum2()
 time.Sleep(time.Second * 10)
}

func calcSum1() {
 runtime.LockOSThread()
 start := time.Now()
 count := 0
 for i := 0; i < 10000000000; i++ {
  count += i
 }
 end := time.Now()
 fmt.Println("calcSum1耗时")
 fmt.Println(end.Sub(start))
 defer runtime.UnlockOSThread()
}

func calcSum2() {
 start := time.Now()
 count := 0
 for i := 0; i < 10000000000; i++ {
  count += i
 }
 end := time.Now()
 fmt.Println("calcSum2耗时")
 fmt.Println(end.Sub(start))
}

输出:

calcSum1耗时
3.295679583s
calcSum2耗时
3.296763125s

看起来没有太大的差别;

但估计在很多个协程(涉及到频繁的调度和切换),但是有一项重要功能需独占一个核,可使用该func


Go LockOSThread[7]




28. UnlockOSThread() 解除go协程与操作系统线程的绑定关系


func UnlockOSThread()

将调用此func的协程,解除和其绑定的操作系统线程

若调用的协程未调用LockOSThread,UnlockOSThread不做操作

从1,10之后,调用了多少次LockOSThread,就要使用UnlockOSThread接触绑定..




29. ThreadCreateProfile() 获取线程创建profile中的记录个数


func ThreadCreateProfile(p []StackRecord) (n int, ok bool)

返回线程创建profile中的记录个数。

  • 如果len(p)>=n,本func就会将profile中的记录复制到p中并返回(n, true)
  • 若len(p)<n,则不会更改p,而只返回(n, false)

绝大多数情况下应当使用runtime/pprof包,而非直接调用ThreadCreateProfile




30. SetBlockProfileRate() 控制阻塞profile记录go协程阻塞事件的采样率


func SetBlockProfileRate(rate int)

SetBlockProfileRate 控制阻塞profile记录go程阻塞事件的采样频率。对于一个阻塞事件,平均每阻塞rate纳秒,阻塞profile记录器就采集一份样本。

  • 要在profile中包括每一个阻塞事件,需传入rate=1
  • 要完全关闭阻塞profile的记录,需传入rate<=0



31. BlockProfile() 返回当前阻塞profile中的记录个数


func BlockProfile(p []BlockProfileRecord) (n int, ok bool)

BlockProfile返回当前阻塞profile中的记录个数

  • 如果len(p)>=n,本函数就会将此profile中的记录复制到p中并返回(n, true)

  • 如果len(p)<n,本函数则不会修改p,而只返回(n, false)

绝大多数情况应当使用runtime/pprof包或testing包的-test.blockprofile标记, 而非直接调用 BlockProfile




参考资料

[1]

运行时 runtime的神奇用法: https://blog.csdn.net/u011525168/article/details/88401166

[2]

golang获取调用者的方法名及所在行数: https://dashen.tech/2018/05/18/golang%E8%8E%B7%E5%8F%96%E8%B0%83%E7%94%A8%E8%80%85%E7%9A%84%E6%96%B9%E6%B3%95%E5%90%8D%E5%8F%8A%E6%89%80%E5%9C%A8%E8%A1%8C%E6%95%B0/

[3]

runtime.Caller的性能问题: https://dashen.tech/2019/11/11/runtime-Caller%E7%9A%84%E6%80%A7%E8%83%BD%E9%97%AE%E9%A2%98/

[4]

Rust vs Go:常用语法对比(13)-将优先权让给其他线程: https://json.dashen.tech/2021/09/14/Rust-vs-Go-%E5%B8%B8%E7%94%A8%E8%AF%AD%E6%B3%95%E5%AF%B9%E6%AF%94-13/

[5]

Go用两个协程交替打印100以内的奇偶数: https://dashen.tech/2022/04/03/Go%E7%94%A8%E4%B8%A4%E4%B8%AA%E5%8D%8F%E7%A8%8B%E4%BA%A4%E6%9B%BF%E6%89%93%E5%8D%B0100%E4%BB%A5%E5%86%85%E7%9A%84%E5%A5%87%E5%81%B6%E6%95%B0/

[6]

Go 应用的性能优化: https://zhuanlan.zhihu.com/p/406826295

[7]

Go LockOSThread: https://dashen.tech/2017/07/11/Go-LockOSThread/

本文由 mdnice 多平台发布

相关文章:

Go Runtime功能初探

以下内容&#xff0c;是对 运行时 runtime的神奇用法[1] 的学习与记录 目录: 1.获取GOROOT环境变量 2.获取GO的版本号 3.获取本机CPU个数 4.设置最大可同时执行的最大CPU数 5.设置cup profile 记录的速录 6.查看cup profile 下一次堆栈跟踪数据 7.立即执行一次垃圾回收 8.给变量…...

01|Oracle学习(监听程序、管理工具、PL/SQL Developer、本地网络服务介绍)

基础概念 监听程序&#xff1a;运行在Oracle服务器端用于侦听客户端请求的程序。 相当于保安&#xff0c;你来找人&#xff0c;他会拦你&#xff0c;问你找谁。他去帮你叫人过来。 配置监听程序应用场景 Oracle数据库软件安装之后没有监听程序&#xff08;服务&#xff09;…...

滴滴数据服务体系建设实践

什么是数据服务化 大数据开发的主要流程分为数据集成、数据开发、数据生产和数据回流四个阶段。数据集成打通了业务系统数据进入大数据环境的通道&#xff0c;通常包含周期性导入离线表、实时采集并清洗导入离线表和实时写入对应数据源三种方式&#xff0c;当前滴滴内部同步中心…...

VBA技术资料MF36:VBA_在Excel中排序

【分享成果&#xff0c;随喜正能量】一个人的气质&#xff0c;并不在容颜和身材&#xff0c;而是所经历过的往事&#xff0c;是内在留下的印迹&#xff0c;令人深沉而安谧。所以&#xff0c;优雅是一种阅历的凝聚&#xff1b;淡然是一段人生的沉淀。时间会让一颗灵魂&#xff0…...

Shell脚本学习3

文章目录 Shell脚本学习3函数函数定义及使用函数参数获取函数返回值 重定向输入输出重定向 其他Here Document/dev/null 文件Shell文件包含获取当前正在执行脚本的绝对路径按特定字符串截取字符串 Shell脚本学习3 函数 函数定义及使用 函数可以让我们将一个复杂功能划分成若…...

代理模式--静态代理和动态代理

1.代理模式 定义&#xff1a;代理模式就是代替对象具备真实对象的功能&#xff0c;并代替真实对象完成相应的操作并且在不改变真实对象源代码的情况下扩展其功能&#xff0c;在某些情况下&#xff0c;⼀个对象不适合或者不能直接引⽤另⼀个对象&#xff0c;⽽代理对象可以在客户…...

C++容器——list的模拟实现

目录 一.list的基本结构 二. 接下来就是对list类构造函数的设计了&#xff1a; 三.链表数据的增加&#xff1a; 四.接下来就是迭代器的创建了&#xff1a; 四.简单函数的实现&#xff1a; 五.构造与析构 六.拷贝构造和赋值重载 传统写法: 现代写法&#xff1a; 七.迭…...

VUE3 祖孙组件传值调用方法

1.在 Vue 3 中&#xff0c;你可以使用 provide/inject 来实现祖孙组件之间的传值和调用方法。 首先&#xff0c;在祖组件中使用 provide 来提供数据或方法&#xff0c;例如&#xff1a; // 祖组件 import { provide } from vue;export default {setup() {const data Hello;c…...

我的网安之路

机缘 我目前从事网安工作,一转眼我从发布的第一篇文章到现在已经过去了4年了,感慨时间过得很快 曾经我是一名Java开发工程师所以我的第一篇文章是跟开发相关的那个时候还是实习生被安排 一个很难的工作是完成地图实时定位以及根据GPS信息模拟海上追捕,这对刚入职的我来说很难 …...

langchain-ChatGLM源码阅读:webui.py

样式定制 使用gradio设置页面的视觉组件和交互逻辑 import gradio as gr import shutilfrom chains.local_doc_qa import LocalDocQA from configs.model_config import * import nltk import models.shared as shared from models.loader.args import parser from models.load…...

<C++>二、 类和对象

1.面向对象和面向过程 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c; 通过函数调用逐步解决问题。 C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将一件事情拆分成不同的对象&#xff0c;靠对象之间的交互完成。 2. C类 C…...

【HttpRunnerManager】搭建接口自动化测试平台实战

目录 一、需要准备的知识点 二、我搭建的环境 三、搭建过程 四、访问链接 五、两个问题点 【整整200集】超超超详细的Python接口自动化测试进阶教程&#xff0c;真实模拟企业项目实战&#xff01;&#xff01; 一、需要准备的知识点 1. linux: 安装 python3、nginx 安装和…...

【adb】adb常用命令

Android Debug Bridge (adb) Android 调试桥 (adb) 是一种功能多样的命令行工具&#xff0c;可让您与设备进行通信。adb 命令可用于执行各种设备操作&#xff0c;例如安装和调试应用。adb 提供对 Unix shell&#xff08;可用来在设备上运行各种命令&#xff09;的访问权限。它…...

SAP 委外副产品业务

SAP 委外副产品业务 1.订单bom设置数量为负 2.采购收货时&#xff0c;副产品O库存增加&#xff0c;545 O 借&#xff1a;原材料 贷&#xff1a;委外加工-发出材料 3.从O库存调拨回本地库存&#xff0c;542...

高并发编程-2. 并发级别

此文章为笔记&#xff0c;为阅读其他文章的感受、补充、记录、练习、汇总&#xff0c;非原创&#xff0c;感谢每个知识分享者。 原文 文章目录 阻塞无饥饿(Starvation-Free)无障碍(Obstruction-Free)无锁(Lock-Free)无等待 由于临界区的存在&#xff0c;多线程之间的并发必须受…...

牛客网Verilog刷题——VL47

牛客网Verilog刷题——VL47 题目答案 题目 实现4bit位宽的格雷码计数器。 电路的接口如下图所示&#xff1a; 输入输出描述&#xff1a; 信号类型输入/输出位宽描述clkwireIntput1时钟信号rst_nwireIntput1异步复位信号&#xff0c;低电平有效gray_outregOutput4输出格雷码计数…...

Redis以及Java使用Redis

一、Redis的安装 Redis是一个基于内存的 key-value 结构数据库。 基于内存存储&#xff0c;读写性能高 适合存储热点数据&#xff08;热点商品、资讯、新闻&#xff09; 企业应用广泛 官网&#xff1a;https://redis.io 中文网&#xff1a;https://www.redis.net.cn/ Redis…...

Apipost教程?一篇文章玩转Apipost

你是否经常遇到接口开发过程中的各种问题&#xff1f;或许你曾为接口测试与调试的繁琐流程而烦恼。不要担心&#xff01;今天我将向大家介绍一款功能强大、易于上手的接口测试工具——Apipost&#xff0c;并带你深入了解如何玩转它&#xff0c;轻松实现接口测试与调试。 什么是…...

微信小程序开发学习之--地图绘制行政区域图

不知道大家有没有感觉就是在做微信小程序地图功能时刚刚接触时候真的感觉好迷茫呀&#xff0c;文档看不懂&#xff0c;资料找不到&#xff0c;就很难受呀&#xff0c;比如我现在的功能就想想绘制出一个区域的轮廓图&#xff0c;主要是为了显眼&#xff0c;效果图如下&#xff1…...

在windows下安装ruby使用gem

在windows下安装ruby使用gem 1.下载安装ruby环境2.使用gem3.gem换源 1.下载安装ruby环境 ruby下载地址 选择合适的版本进行下载和安装&#xff1a; 在安装的时候&#xff0c;请勾选Add Ruby executables to your PATH这个选项&#xff0c;添加环境变量&#xff1a; 安装Ruby成…...

【Ajax】笔记-设置CORS响应头实现跨域

CORS CORS CORS是什么&#xff1f; CORS(Cross-Origin Resource Sharing),跨域资源共享。CORS是官方的跨域解决方案&#xff0c;它的特点是不需要在客户端做任何特殊的操作&#xff0c;完全在服务器中进行处理&#xff0c;支持get和post请求。跨域资源共享标准新增了一组HTTP首…...

实现Feed流的三种模式:拉模式、推模式和推拉结合模式

在互联网产品中&#xff0c;Feed流是一种常见的功能&#xff0c;它可以帮助我们实时获取我们关注的用户的最新动态。Feed流的实现有多种模式&#xff0c;包括拉模式、推模式和推拉结合模式。在本文中&#xff0c;我们将详细介绍这三种模式&#xff0c;并通过Java代码示例来实现…...

Vue中使用Typescript及Typescript基础

准备工作 新建一个基于ts的vue项目 通过官方脚手架构建安装 # 1. 如果没有安装 Vue CLI 就先安装 npm install --global vue/cli最新的Vue CLI工具允许开发者 使用 TypeScript 集成环境 创建新项目。 只需运行vue create my-app 然后选择选项&#xff0c;箭头键选择 Manuall…...

MySQL数据库 【索引事务】

目录 一、概念 二、索引的优缺点 1、索引的优点 2、索引的缺陷 三、索引的使用 1、查看索引 2、创建索引 3、删除索引 四、索引底层的数据结构 1、B树 2、B树 五、索引事务 1、概念和回滚 2、事务的使用 3、事务的基本特性 4、并发会遇到的问题 &#xff08…...

源码阅读:classnames

源码阅读&#xff1a;classnames 源码阅读&#xff1a;classnames简介源码解读indexdedupebind类型声明 学习与收获 源码阅读&#xff1a;classnames 简介 classnames 一个简单的 JavaScript 实用程序&#xff0c;用于有条件地将类名连接在一起。 可以通过 npm 包管理器从 n…...

【解惑笔记】树莓派+OpenCV+YOLOv5目标检测(Pytorch框架)

【学习资料】 子豪兄的零基础树莓派教程https://github.com/TommyZihao/ZihaoTutorialOfRaspberryPi/blob/master/%E7%AC%AC2%E8%AE%B2%EF%BC%9A%E6%A0%91%E8%8E%93%E6%B4%BE%E6%96%B0%E6%89%8B%E6%97%A0%E7%97%9B%E5%BC%80%E6%9C%BA%E6%8C%87%E5%8D%97.md#%E7%83%A7%E5%BD%95…...

PostgreSQL中如何配置Huge page的数量

在了解如在PG中如何配置大页之前&#xff0c;我们先要对大页进行一定的了解&#xff0c;为什么要配置大页&#xff0c;配置大页的好处有哪些。 我们日常的操作系统中&#xff0c;程序不直接使用内存&#xff0c;而是使用虚拟内存地址来处理内存分配&#xff0c;避免计算的复杂…...

Mysql之binlog日志浅析

一、binlog日志简介 Binlog是MySQL数据库中的二进制日志&#xff0c;用于记录数据库中所有修改操作&#xff0c;包括增删改等操作。binlog以二进制格式保存&#xff0c;可以通过解析binlog文件来查看数据库的操作历史记录。binlog日志可以用于数据恢复、数据备份、数据同步等场…...

js 生成器函数

生成器函数&#xff08;Generator Function&#xff09;&#xff1a;生成器函数是一种特殊的函数&#xff0c;可以通过yield关键字来暂停和恢复函数的执行&#xff0c;从而实现惰性计算和迭代器的功能。在例子中&#xff0c;我们定义了一个fibonacci生成器函数&#xff0c;它使…...

HCIP OSPF+BGP综合实验

题目 1、该拓扑为公司网络&#xff0c;其中包括公司总部、公司分部以及公司骨干网&#xff0c;不包含运营商公网部分。 2、设备名称均使用拓扑上名称改名&#xff0c;并且区分大小写。 3、整张拓扑均使用私网地址进行配置。 4、整张网络中&#xff0c;运行OSPF协议或者BGP协议…...