Go工程师体系课 001【学习笔记】

转型

  1. 想在短时间系统转到Go工程理由
  2. 提高CRUD,无自研框架经验
  3. 拔高技术深度,做专、做精需求的同学
  4. 进阶工程化,拥有良好开发规范和管理能力的

工程化的重要性

高级开的期望

  1. 良好的代码规范
  2. 深入底层原理
  3. 熟悉架构
  4. 熟悉k8s的基础架构

扩展知识广度,知识的深度,规范的开发体系

四个大的阶段

  1. go语言基础
  2. 微服务开发的(电商项目实战)
  3. 自研微服务
  4. 自研然后重构

领域

  1. Web开发-gin、beego等
  2. 容器虚拟化-docker、k8s、istio
  3. 中间件- etcd、tidb、influxdb、nsq等
  4. 区块链 以太坊、fabric
  5. 1xAR5 - go-zero, dapr, rpcx, kratos. dubbo-go,

相关环境的开发

  1. go安装 官网
  2. goland、vscdoe
# go build hello.go
# ./hello
go run hello.go

golang新版本中同一个包下有多个main运行时,运行工具中选择运行类型file, go中不推荐一个目录中有两个main文件,如果需要有多个main放到不同的文件夹中

变量

package main

import "fmt"

// 全局变量 定义可以不使用
var name = "bobby"
var age = 19
var ok bool

func main() {
 // go是静态语言 静态语言和动态语言相比变量差异很大
 // 1. 变更定义 先定义后使用 类型声明了不能改变
 // 定义变量
 var name string = "hello world"
 age := 1
 // go 语言中变量定义的不使用是不行的,强制性的
 fmt.Println(name, age)

 // 多变量定义
 var user1, user2, user3 = "booby1", "bobby2", 22
 fmt.Println(user1, user2, user3)

 // 注意变量先定义才能使用
 // 是静态语言类型和赋值要一致
 // 要求和规范性会更好
 // 变量名不能冲突 同一个代码块中不能同名的
 // 简洁定义 名:=值 不能用于全局定义
 // 变量是有0值的

}

变量

package main

import "fmt"

func main() {
 // 常量 定义的时候就要指定值,不能修改
 const PI float32 = 3.14159265358979323846 // 显式定义
 const PI2 = 3.14159265358979323846        // 常量 大写多单词 用下划线
 const (
  UNKNOW = 1
  FEMALE = 2
  MALE   = 3
 )
 //没有设置类型和值它会沿用前面的值
 const (
  x int = 16
  y
  s = "abc"
  z
 )
 fmt.Println(x, y, z)
 /**
 常量类型只要以是bool 数值(整数、浮点)
 不曾使用的常量,没有强制使用的
 显示指定类型的时候,必须确保左右类型一致
 */
}

iota

特殊常量

package main

import "fmt"

func main() {
 // 匿名变量 定义一个变更不使用它
 var _ int // 接收返回值进,点位符只接收哪个值
 // iota 特殊常量,可以认为是一种可以被编译器修改的常量
 const (
  ERR1 = iota + 1
  ERR2
  ERR25 = "ha" // iota内部仍然增加计数
  ERR3
  ERR4 = iota
 )
 const (
  ERRORNEW1 = iota // 从0开始计数
 )
 /*
   如果中断了iota那么要显示的恢复,后续会自动递增 自增类型默认是int类型的
   iota能简化const类型的定义
 */
 fmt.Println(ERR1, ERR2, ERR3, ERR4)
}

变量的作用域

个人感觉注意代码块的范围还有就是变量名:=值这种方式的声明

go的基本数据类型

  • 基本数据类型
  • bool true false
  • 数值类型
    • 整数 int8~64 uint8~64(无符号数)
    • 浮点数 float32 float64
    • 复数
    • byte字节 uint8
    • rune类型 int32
  • 字符和string
package main

import "fmt"

func main() {
 var a int8
 var b int16
 var c int32
 var d int64

 var ua uint8
 var ub uint16
 var uc uint32

 var ch3 byte
 c = 'a'         // 字符类型
 var int_32 rune // 也是字符

 int_32 = '中' // 即有中文也有英文
 fmt.Println(int_32)

 fmt.Print("c=%c", ch3)

 var str string
 str = "i am bobby"
 fmt.Println(str)


} 

各种类型的间的转换

 var str string
 str = "i am bobby"
 fmt.Println(str)
 // 字符串转数字
 var istr = "12"
 myint, err := strconv.Atoi(istr)
 if err != nil {
  fmt.Println("convert error")
 }
 fmt.Println(myint)

 var myi = 32
 mstr := strconv.Itoa(myi)
 fmt.Println(mstr)

 // 字符串转float32 转换bool
 mFloat, error := strconv.ParseFloat("3.1415", 64)
 if error != nil {
  fmt.Println("convert error")
 }
 fmt.Println(mFloat)
 parseBol, err := strconv.ParseBool("true")
 if err != nil {
  fmt.Println("convert error")
 }
 fmt.Println(parseBol)
 // 基本类型转字符串
 boolStr := strconv.FormatBool(true)
 fmt.Println(boolStr)

 floatStr := strconv.FormatFloat(3.1415, 'E', -1, 64)
 fmt.Println(floatStr)

运算符

go语言提供哪些集合类型

package main

import "fmt"

func main() {
 // 数组 slice map list
 // 数组 var name [count]int
 //var course1 [3]string // course1是类型 只有3个元素的数组类型
 //var course2 [4]string
 //course1[0] = "og"
 //course1[1] = "grpc"
 //course1[2] = "gin"
 ////[]strig 和 [3]string 这是两种不同的类型
 //fmt.Println("%T \r\n", course1)
 //fmt.Println("%T \r\n", course2)
 //
 //for _, value := range course1 {
 // fmt.Printf("value=%s\n", value)
 //}

 // 初始化
 course1 := [3]string{"go", "grpc", "gin"}
 //course1 := [3]string{2:"gin"}
 // course3:=[...]string{"go","grpc","gin"}
 for _, value := range course1 {
  fmt.Printf("value=%s\n", value)
 }

 // 数组元素长度及内容一样,可以直接用等于判断
 // 多维数组
 var courseInfo [3][4]string
 courseInfo[0] = [4]string{"go", "1h", "bobby", "go体系课"}
 courseInfo[1] = [4]string{"grpc", "2h", "bobby1", "grpc入门"}
 courseInfo[2] = [4]string{"gin", "2h", "bobby2", "gin高级开发"}

 for i := 0; i < len(courseInfo); i++ {
  for j := 0; j < len(courseInfo[i]); j++ {
   fmt.Print(courseInfo[i][j] + "")
  }
  fmt.Println()
 }
}

切片

package main

import "fmt"

func main() {
 // 理解为动态的array 弱化数组的概念 切片的本质存储和数组是有区别
 var courses []string
 fmt.Printf("%T \r\n", courses)

 // 这个方法很特别 添加元素
 courses = append(courses, "go")
 courses = append(courses, "grpc")
 courses = append(courses, "gin")

 fmt.Println(courses[1])

 // 初始化 3种 1:从数组直接创建 2:使用string{} 3:make
 allCourses := [5]string{"go", "grpc", "gin", "mysql", "elasticsearch"}
 courseSlice := allCourses[0:2] // 左闭右开的区间 python的语法
 fmt.Println(courseSlice)

 courseSlice1 := []string{"go", "grpc", "gin", "mysql", "elasticsearch"}
 fmt.Println(courseSlice1)
 courseSlice2 := make([]string, 3)
 courseSlice2[0] = "C"
 fmt.Println(courseSlice2)

 // 如何访问切片的元素 访问单个(类似数组,不能超长度) 访问多个allCourses[start:end] 前闭后开,如果只有start(到结束)
 // 如果没有start有end (从end之前的元素)
 // 没有【:] 相当于复制了一份
 cSlice1 := []string{"go", "grpc"}
 cSlice2 := []string{"mysql", "es", "gin"}
 c12 := append(cSlice1, cSlice2[1:]...)
 fmt.Println(c12)
}
package main

import (
 "fmt"
 "strconv"
 "unsafe"
)

func printSlice(data []string) {
 data[0] = "java"
 for i := 0; i < 10; i++ {
  data = append(data, strconv.Itoa(i))
 }
}

// 定义slice会通过结构体去定义
type slice struct {
 array unsafe.Pointer
 len   int
 cap   int
}

func main() {
 //go的slice在函数 参数传递的时候是值传递还是引用传递,值传递,效果又呈现出引用的效果(不完全是)
 course := []string{"go", "grpc", "gin"}
 printSlice(course)
 fmt.Println(course)
}

defer 是有能力修改返回值的
error,panic,recover go语言的错误处理理念, 一个函数可能出错,开发函数的人需要有一个返回值去告诉调用者是否成功,要求我们必须要处理这个error
go设计者认为必须要处理这个error,防御型编辑

func A() (int,error){
  panic("this is an panic") // panic会导致程序的退出,平时开发中不要随便使用,一般我人在哪里用到, 我们一个服务启动的过程中 

  return 0,errors.New("this is an error")
}

结构体

 package main

type Person struct {
 name    string
 age     int
 address string
 height  float32
}

type Person1 struct {
 name string
 age  int
}

type Student struct {
 //// 第一种嵌套方式
 //p     Person1
 //第二种方式 访问的方式可以直接.name .age 但初始化时不能 类似覆盖的方式
 Person1
 score float32
}

func main() {
 // 如何初始化结构体
 p1 := Person{"bobby1", 18, "七星高照", 1.80}
 p2 := Person{name: "bobby2", height: 1.90}
 var persons []Person
 persons = append(persons, p1)
 persons = append(persons, p2)
 persons = append(persons, Person{
  name: "bobby3",
 })
 // 匿名结构体
 address := struct {
  province string
  city     string
  address  string
 }{
  province: "北京市",
  city:     "通州区",
  address:  "万寿路",
 }

 // 结构体的嵌套
 s := Student{
  p: Person1{
   name: "bob",
   age:  18,
  },
  score: 95.6,
 }
 s.p.name = "zzz"
}
// 结构体绑定方法
 // func(s StructType)funcName(param1 paramType,....)(returnTypes1....){.....}

指针

// 第一种初始化方式
ps := &Person{}

// 第二种初始化方式
var emptyPerson Person
pi := &emptyPerson

// 第三种初始化方式
var pp = new(Person)
fmt.Println(pp.name)

// 初始化两个关键字,map、channel、slice 初始化推荐使用 make 方法
// 指针初始化推荐使用 new 函数,指针要初始化否则会出现 nil pointer
// map 必须初始化

指针传递交换两个值

package main

import "fmt"

// 通过指针交换两个值
func swap(a, b *int) {
 *a, *b = *b, *a
}

func main() {
 x, y := 10, 20

 fmt.Printf("交换前: x = %d, y = %d\n", x, y)

 // 调用 swap 函数交换值
 swap(&x, &y)

 fmt.Printf("交换后: x = %d, y = %d\n", x, y)
}

go语言中的nil

/*
不同类型的数据零值不一样

bool       false
numbers    0
string     ""
pointer    nil
slice      nil
map        nil
channel、interface、function nil

struct 默认值不是 nil,默认值是具体字段的默认值
*/
package main

import "fmt"

type Person struct {
    name string
    age  int
}

func main() {
  // struct每一个值都等于才相当
    p1 := Person{
        name: "bobby",
        age:  18,
    }

    p2 := Person{
        name: "bobby",
        age:  18,
    }

    if p1 == p2 {
        fmt.Println("yes")
    }
}

go鸭子

// Go语言的接口,鸭子类型,php,python
// Go语言中处处都是interface,到处都是鸭子类型 duck typing

/*
当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来也像鸭子,那么这只鸟就是鸭子
动词,方法,具备某些方法
*/
// 主要是声明方法的定义
type Duck interface {
  // 方法的申请
  Gaga()
  Walk()
  Swimming()
}
type pskDuck struct {
  legs int
}
func (pd *pskDuck) Gaga(){
  fmt.Println("嘎嘎")
}
func (pd *pskDuck) Walk(){
  fmt.Println("嘎嘎")
}
func (pd *pskDuck) Swimming (){
  fmt.Println("嘎嘎")
}

go语言的包组织

同一个文件夹下不允许出现不同名的pacage, 导入是使用路径,使用时候是包名+类包,别名引用;.全部引入到当前包中使用, 初始化的时候会使用func init(){}, go.mod是自动维护的gin-gonic/gin
新版都是使用go modules go list -m all

go list -m --version 包名 可以查看版本
go get 指定包名
go mod tidy # go mod help 来查看
# go install 安装
# go get -u 升级到最新的将要版本或修定版本
# # go get 会修改go.mod文件的
# go get github.com/go-redis/redis/v8@version 

代码规范

1. 代码规范

命名

  • 使用驼峰命名法,例如:myVariable
  • 包名应为小写单词,无需使用下划线或混合大小写。
  • 接口命名以 -er 结尾,例如:ReaderWriter

格式化

  • 使用 gofmt 工具统一代码格式。
  • 保持代码风格一致,如缩进、空格和换行等。

注释

  • 使用行注释 // 为函数、方法和复杂逻辑添加说明。
  • 包级注释应放在包声明之前。

2. 结构体与接口

结构体

  • 尽量使用小写字段名,除非需要导出。
  • 使用构造函数初始化结构体。

接口

  • 定义小而简单的接口。
  • 使用接口满足需要,而不是定义大而全的接口。

3. 错误处理

错误检查

  • 始终检查函数返回的错误。
  • 使用 errors.Newfmt.Errorf 创建错误信息。

错误处理

  • 错误应尽早处理,避免延迟检查。
  • 当错误不可恢复时,使用 log.Fatal 记录日志并退出。
  • 尽量提供上下文信息以帮助调试。

为什么要代码规范

  1. 代码规范并不是强制的,但是不同的语言一些细微的规范还是要遵循的。
  2. 代码规范主要是为方便团队内形成一个统一的代码风格,提高代码的可读性、统一性。

1. 代码规范

1.1 命名规范

包名

  1. 尽量和目录保持一致。
  2. 尽量采取有意义的包名,简短。
  3. 不要和标准库名冲突。
  4. 包名采用全部小写。

文件名

  • 使用 user_name.go,如果有多个单词可以采用蛇形命名法。

变量名

  1. 蛇形:适用于 python、php。
  2. 驼峰:适用于 java、c、go。
  3. userName
  4. UserName
  5. un string //unad userNameAndDesc
  6. 有一些专有命名,如 URLVersion
  7. bool 类型使用 Hasiscanallow 等前缀。

1.2 结构体命名

  • 使用驼峰,例如 User

1.3 接口命名

  • 接口命令基本上和结构体差不多。

单元测试

多线程

// python, java, php 多线程编程、多进程编程,多线程和多进程存在的问题主要是耗费内存
// 内存、线程切换、web2.0,用户级线程,绿程,轻量级线程,协程,asyncio-python php-swoole java - netty
// 内存占用小(2k)、切换快,go语言的协程,go语言诞生之后就只有协程可用 goroutine
// 主协程退出,在其中启动的协程就会死掉
package main

import (
 "fmt"
 "time"
)

func asyncPrint() {
 fmt.Println("bobby")
}

func main() {
 // 主死随从
 //go asyncPrint()
 // 1. 闭包 2. for循环的问题
 for i := 0; i < 100; i++ {
  go func(i int) {
   for {
    time.Sleep(time.Second)
    fmt.Println(i)
   }
  }(i)
 }

 fmt.Println("main goroutine")
 time.Sleep(2 * time.Second)
}

理解GMP

wg (wait group)

虽然我们可以使用主线程sleeep来保证主协程不死,但主不一定知道要sleep要多久,子goroutine如何通知到主的goroutine自己结束了,主的goroutine如何知道子的已经结束了

package main

import (
 "fmt"
 "sync"
)

func main() {
 var wg sync.WaitGroup

 // 我要监控多少个goroutine执行结束
 wg.Add(100)
 for i := 0; i < 100; i++ {
  go func(i int) {
   defer wg.Done()
   fmt.Println(i)
   //wg.Done() // 调用了要调用一次done
  }(i)
 }
 // 等到100都执行完
 wg.Wait()

 fmt.Println("all done")
 // waitgroup主要用于goroutine的执行等待 add方法要和done要和Done方法配套
}

goroutine如何使用锁

package main

import (
 "fmt"
 "sync"
)

//锁 资源竞争

var total int
var wg sync.WaitGroup
var lock sync.Mutex // 只要是同一把锁就没有问题

func add() {
 defer wg.Done()
 for i := 0; i < 100000; i++ {
  lock.Lock()
  total += 1
  lock.Unlock()
 }
}

func sub() {
 defer wg.Done()
 for i := 0; i < 100000; i++ {
  lock.Lock()
  total -= 1
  lock.Unlock()
 }
}

func main() {
 wg.Add(2)
 //两个同时运行时,因为不是原子操作它会产生资源竞争,导致结果不一致
 go add()
 go sub()
 wg.Wait()

 fmt.Println("done i=", total)
}
// 如果权权是加一或者减一操作
// 可以使用atomic.AddInXX

读写锁

上面我们已经学习了互斥(本质上是将并行的内容,串行化),使用lock肯定会影响性能
即使是设计锁,那么也要尽量保证并行
我们有两组协程,一组写,一组读,web系统中绝大数是读多写少,比如详情页面
虽然有多个goroutine,但仔细分析我们会发现 读协程之间应该是并发,读和写之间应该是串行

package main

import (
 "fmt"
 "sync"
 "time"
)

func main() {
 var num int
 var rwLocak sync.RWMutex
 var wg sync.WaitGroup

 wg.Add(2)

 go func() {
  defer wg.Done()
  // 负责写数据
  rwLocak.Lock() // 加写锁 写锁会防止 别的写锁获取和读锁获取
  defer rwLocak.Unlock()
  num = 12
 }()

 // 因为不能保证 写先执行(因为还没讲到goroutine之间的通信)我们先sleep一下吧
 time.Sleep(time.Second)
 // 读取的goroutine
 go func() {
  defer wg.Done()
  rwLocak.RLock() // 加读锁 ,读锁不会阻止别人的读
  defer rwLocak.RUnlock()
  fmt.Println(num)
 }()
 wg.Wait()
}

goroutine之间进行通信

package main

import "fmt"

func main() {
 /*
    不要通过共享内存来通信,而要通过通信来实现内存共享
    php python java 多线程编程的时候,丙从此goroutine之间通信最常用的方式是一个全局可以提供消息的机制
    python-queue java生产者消费者
    channel 在加上语法糖让使用channel更加简单
 */
 var msg chan string //可以理解是一个通道 还要定义通道的类型
 // 它底层是数组来完成,所以要初始一下
 msg = make(chan string, 1) // channel的初始化值为0时,你放的值会阻塞 deadlock
 // msg = make(chan string, 0)  // 无缓冲
 msg <- "bobby" // 放值 右边的值放到这个channel中
 data := <-msg  // 拿值
 fmt.Println(data)
}

有缓冲和缓冲

  • 无缓冲 适用于通知 B要第一时间知道A是否已经完成
  • 有缓冲 适用于生产者之间通信
// 消息传递,消息过滤
// 信号广播
// 事件订阅和广播
// 任务分发
// 结果汇总
// 并发控制
// 同步和异步
package main

import (
 "fmt"
 "time"
)

func main() {
 var msg chan int
 msg = make(chan int, 2)
 go func(msg chan int) {
  for data := range msg {
   fmt.Println(data)
  }
  fmt.Println("all done")
 }(msg)

 msg <- 1 // 放值channel中
 msg <- 2
 close(msg) // 关闭这个通道
 d := <-msg
 fmt.Println(d) // 已经关闭的channel可以继续取值,但不能再放值了
 msg <- 3       // 已经关闭的channel不能再放值了

 time.Sleep(time.Second * 10)

}

单向channel

package main

import (
 "fmt"
 "time"
)

func producer(out chan<- int) {
 for i := 0; i < 10; i++ {
  out <- i * i
 }
 close(out)
}

func consumer(in <-chan int) {
 for num := range in {
  fmt.Printf("num=%d\r\n", num)
 }
}

func main() {
 // 默认情况下,channel是双向的
 // 但是我们经常一个channel做为一个参数时,不希望往里写数据
 // 单向channel
 //var ch1 chan int       // 双向的
 //var ch2 chan<- float64 // 单向的只能写入float64
 //var ch3 <-chan int     // 单向的,只能读取数据

 //c := make(chan int, 3)
 //var send chan<- int = c // send-only
 //var read <-chan int = c // rec-oney
 //send <- 1
 //<-send //这样就不行了,只能写不能读
 //<-read
 // 不能将单向的转成普通的channel
 c := make(chan int)
 go producer(c)

 go consumer(c)

 time.Sleep(time.Second * 10)

}

一道常见面试题

package main

import (
 "fmt"
 "time"
)

var number, latter = make(chan bool), make(chan bool)

func printNum() {
 i := 1
 for {
  // 我怎么去做到,应该此片,等待另一个goroutine来通知我
  <-number
  fmt.Printf("%d%d", i, i+1)
  i += 2
  latter <- true
 }
}

func printLetter() {
 i := 0
 str := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 for {
  // 我怎么去做到,应该此片,等待另一个goroutine来通知我
  <-latter
  if i >= len(str) {
   return
  }
  fmt.Print(str[i : i+2])
  i += 2
  number <- true
 }
}

func main() {
 /*
  使用两个goroutine交替打印序列,一个goroutine打印数字,另外一个goroutine打印字母,最终效果如下:
  12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ2728
 */
 go printNum()
 go printLetter()
 number <- true
 time.Sleep(time.Second * 100)
}

监控

package main

import (
 "fmt"
 "sync"
 "time"
)

var done bool
var lock sync.Mutex

func go1() {
 time.Sleep(time.Second)
 lock.Lock()
 defer lock.Unlock()
 done = true
}

func go2() {
 time.Sleep(time.Second * 2)
 lock.Lock()
 defer lock.Unlock()
 done = true
}

func main() {
 // 类似于switch 但select的功能和我们操作系统linux里面提供的io的select poll epoll类似
 // select 主要作用于多个channel
 // 现在有需求,我们现在有两个goroutine都在执行,但是我们在主的goroutine中,当某一个执行完成了,这个时间我会立以知道
 go go1()
 go go2()
 for {
  time.Sleep(time.Millisecond * 10)
  if done {
   fmt.Println("done")
   return
  }
 }
}

更推崇消息的方式来通知,而不是共享变量的方式

context

并发编程中使用最多一个场景了

package main

import (
 "fmt"
 "sync"
 "time"
)

var wg sync.WaitGroup
var stop bool

// 我们新的需求,我可以主动退出监控程序
// 共享变量
func cupInfo() {
 defer wg.Done()
 for {
  if stop {
   break
  }
  time.Sleep(2 * time.Second)
  fmt.Println("CPU的信息 ")
 }
}

func main() {
 // 为什么使用context
 // 有一个goroutine监控cpu的信息
 wg.Add(1)
 go cupInfo()
 time.Sleep(6 * time.Second)
 stop = true
 wg.Wait()
 fmt.Println("监控完成")
}
// 演进
package main

import (
 "context"
 "fmt"
 "sync"
 "time"
)

var wg sync.WaitGroup

// var stop bool
//var stop = make(chan struct{})

// 我们新的需求,我可以主动退出监控程序
// 共享变量
func cupInfo(ctx context.Context) {
 defer wg.Done()
 for {
  select {
  case <-ctx.Done():
   fmt.Println("退出cpu监控")
   return
  default:
   time.Sleep(2 * time.Second)
   fmt.Println("CPU的信息 ")
  }

 }
}

func main() {
 // 为什么使用context
 // 有一个goroutine监控cpu的信息
 //var stop = make(chan struct{})
 wg.Add(1)
 ctx, cancel := context.WithCancel(context.Background())
 go cupInfo(ctx)
 time.Sleep(6 * time.Second)
 //stop <- struct{}{}
 cancel()
 wg.Wait()
 fmt.Println("监控完成")
}
package main

import (
 "context"
 "fmt"
 "sync"
 "time"
)

var wg sync.WaitGroup

// var stop bool
//var stop = make(chan struct{})

// 我们新的需求,我可以主动退出监控程序
// 共享变量
func cupInfo(ctx context.Context) {
 defer wg.Done()
 for {
  select {
  case <-ctx.Done():
   fmt.Println("退出cpu监控")
   return
  default:
   time.Sleep(2 * time.Second)
   fmt.Println("CPU的信息 ")
  }

 }
}

func main() {
 // 为什么使用context
 // 有一个goroutine监控cpu的信息
 //var stop = make(chan struct{})
 wg.Add(1)
 //ctx, cancel := context.WithCancel(context.Background())
 ctx, _ := context.WithTimeout(context.Background(), 6*time.Second)
 go cupInfo(ctx)
 //time.Sleep(6 * time.Second)
 //stop <- struct{}{}
 //cancel()
 wg.Wait()
 fmt.Println("监控完成")
}

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://joyjs.cn/archives/4768

(0)
Walker的头像Walker
上一篇 2025年11月25日 01:00
下一篇 2025年11月24日 02:00

相关推荐

  • Go工程师体系课 014【学习笔记】

    rocketmq 快速入门 去我们的各种配置(podman)看是怎么安装的 概念介绍 RocketMQ 是阿里开源、Apache 顶级项目的分布式消息中间件,核心组件: NameServer:服务发现与路由 Broker:消息存储、投递、拉取 Producer:消息生产者(发送消息) Consumer:消息消费者(订阅并消费消息) Topic/Tag:主题/…

    个人 2025年11月25日
    3600
  • 深入理解ES6 009【学习笔记】

    javascript中的类 function PersonType(name){ this.name = name; } PersonType.prototype.sayName = function(){ console.log(this.name) } var person = new PersonType("Nicholas") p…

    个人 2025年3月8日
    1.1K00
  • Go工程师体系课 002【学习笔记】

    GOPATH 与 Go Modules 的区别 1. 概念 GOPATH 是 Go 的早期依赖管理机制。 所有的 Go 项目和依赖包必须放在 GOPATH 目录中(默认是 ~/go)。 一定要设置 GO111MODULE=off 项目路径必须按照 src/包名 的结构组织。 不支持版本控制,依赖管理需要手动处理(例如 go get)。 查找依赖包的顺序是 g…

    个人 2025年11月25日
    5200
  • 深入理解ES6 012【学习笔记】

    代理(Proxy)和反射(Reflection)API 代理是一种可以拦截并改变底层javascript引擎操作的包装器,在新语言中通过它暴露内部运作对象,从而让开发者可以创建内建的对象。 代理陷阱 覆写的特性 默认特性 get 读取一个属性值 Reflect.get() set 写入一个属性值 Reflect.set() has in操作符 Reflect…

    个人 2025年3月8日
    96900
  • Go工程师体系课 008【学习笔记】

    订单及购物车 先从库存服务中将 srv 的服务代码框架复制过来,查找替换对应的名称(order_srv) 加密技术基础 对称加密(Symmetric Encryption) 原理: 使用同一个密钥进行加密和解密 就像一把钥匙,既能锁门也能开门 加密速度快,适合大量数据传输 使用场景: 本地文件加密 数据库内容加密 大量数据传输时的内容加密 内部系统间的快速通…

    个人 2025年11月25日
    4900

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
欢迎🌹 Coding never stops, keep learning! 💡💻 光临🌹