Go

0x01 准备

  • Go 语言是一种静态的、强类型编译型并发型、并具有垃圾回收功能的编程语言

  • Go 编译器编译流程

    graph LR 词法解析-->语法解析 -->抽象语法树构建 -->类型检查 -->变量捕获 -->函数内联 -->逃逸分析 -->闭包重写 -->遍历并编译函数 -->SSA生成 -->机器码生成
  • Go 编译器安装(Windows)

    • 在 Go 语言官网下载.msi安装包
    • 安装完成后配置用户环境变量
      • 变量名:GOPATH
      • 变量值:C:\Program Files\Go(默认路径)
    • 使用命令 go env 确认是否安装成功
      • 修改代理地址: go env -w GOPROXY=https://goproxy.cn
  • 创建 GoLand 项目

    • 新建项目(选择不含(GOPATH)的项目)
      • 设置 GOROOT(使用命令 go env GOROOT 命令查看)
      • 设置环境:GOPROXY=https://goproxy.cn,direct
    • 在设置 -> GOPATH 中设置项目和模块的 GOPATH 全部为空,并选中以下两个选项
      • 使用系统环境中定义的 GOPATH
      • 为整个 GOPATH 编制索引
    • 在项目根目录下使用命令go get -u github.com/gin-gonic/gin
    • 在 go.mod 中添加require github.com/gin-gonic/gin latest
    • 在项目根目录下使用命令go mod tidy

0x02 基础语法

(1)Hello World

  • 创建文件 main.go 并写入以下代码:

    package main
    
    import "fmt"
    
    func main() {
    	fmt.Println("Hello World")
    }
    
  • package main:package 类型可以理解为工作空间,便于为程序归类,达到模块化,有以下分类

    • 可执行包:产生可执行文件
      • main 包代表可以被编译和执行,必须有 main 函数作为入口点
    • 依赖包:功能特定,可重复使用
  • import "fmt":导入标准库

  • func:声明函数

(2)内置数据类型

  • 有符号整型:int/int8/int16/int32/int64
  • 无符号整型:uint/uint8/uint16/uint32/uint64/uintptr
  • 浮点数型:float32/float64
  • 复数型:complex64/complex128
    • real:获取实部
    • image:获取虚部
  • 布尔型:bool
  • 字节型:byte
  • UTF-8 编码值:rune
  • 字符串型:string

(3)变量与常量

  • 变量:执行环境中的事件存储区域

  • 变量的声明与赋值

    • 一般情况:var name type = expression

      func main() {
      	var hw string = "Hello World"
      	fmt.Println(hw)
      }
      
      • 可以在单个语句中声明并赋值多个不同类型的变量

        func main() {
        	var i, j, k = 1, true, "123"
        	fmt.Println(i, j, k)
        }
        
    • 短类型:name := expression

      func main() {
      	hw := "Hello World"
      	fmt.Println(hw)
      }
      
  • 变量仅在被定义的作用域内有效

    • 作用域:Universe > Package > File > Function

(4)表达式与运算符

  • 表达式:编程语言中的句法实体,可以对其进行计算以确定其值。表达式可以是单个或多个变量或常量,也可以是变量、函数、操作符的组合
  • 运算符
    • 算术运算符:+/-/*///%
    • 关系运算符:==/!=/>/</>=/<=
    • 逻辑运算符:&&/||/
    • 赋值运算符:=/+=/-=/*=//=/%=/&=/^=/|=
    • 位运算符:&/^/|/>>/<<
  • 注释
    • 单行注释://
    • 多行注释:/**/

(5)程序控制结构

a. 顺序结构

  • 从上到下依次执行各个语句

b. 选择结构

  • if 语句

    • 两路分支结构

      if exp {
          // code1
      } else {
          // code2
      }
      
    • 多路分支结构

      if exp1 {
          // code1
      } else if exp2 {
          // code2
      } else {
          // code3
      }
      
  • switch 语句

    • 按值

      switch var1 {
          case val1:
          	// code
          case val2, val3:
          	// code
          default:
          	// code
      }
      
    • 按条件

      switch {
          case exp1:
          	// code
          case exp2:
          	// code
          default:
          	// code
      }
      

c. 循环结构

  • 标准

    for 表达式1; 表达式2; 表达式3 {
        // code
    }
    

    如:

    for i := 0; i < 10; i++ {
    	fmt.Println(i)
    }
    
  • 只有条件判断,如

    for i < 100 {
    	fmt.Println(i)
    	i = i * 2
    }
    
  • 无限循环,如

    for {
    	fmt.Println("HW")
    }
    
  • range,如

    values := []int{1, 3, 56, 2}
    for i, v := range values {
    	fmt.Println(i, v)
    }
    
  • 其他循环控制语句

    • break:跳出循环
    • continue:跳过本次循环
    • label:标识某个指定位置,常见于与 continue 搭配执行跳转

(6)复合类型

a. 数组

  • 一维数组:var arr = []int{}

  • 二维数组:var arr = [][]int{}

  • 声明数组:

    var arr1 [10]int
    var arr2 = [4]int{1, 2, 3, 4}
    var arr3 = [12]int{1, 5: 4, 6, 10: 100, 15}
    var arr4 = [...]int{1, 2, 3}
    

b. 切片

  • 声明切片

    var slice1 []int
    var slice2 = []int{1, 2, 3}
    var slice3 = []int{1, 5: 4, 6, 10: 100, 15}
    var slice4 = make([]int, 5)
    var slice5 == make([]int, 5, 7)
    
  • 切片添加

    var slice []int
    fmt.Println(slice)
    slice = append(slice, 1, 2, 3)
    fmt.Println(slice)
    
  • 切片截取

    foo := make([]int, 5)
    foo[3] = 3
    foo[4] = 4
    fmt.Println(foo)
    
    bar := foo[1:4]
    fmt.Println(bar)
    
    bar[1] = 1
    fmt.Println(bar)
    

c. 哈希表

  • 哈希值通过哈希函数映射到表

    hash = f(key)
    index = hash % array_size
    
  • 哈希冲突:不同的哈希值经过哈希函数计算结果相同

    • 解决方法:拉链法、开放寻址法 、
  • 使用

    • 定义

      var hash map[T]T
      var hash = make(map[T]T, NUMBER)
      

      var hash = map[string]int{ "a": 1, "b": 2 }
      
    • 赋值

      hash[key] = value
      

      hash["c"] = 3
      
    • 访问

      i, v := hash[key]
      

      i, v := hash["a"]
      
  • 哈希的 key 必须可比较

    • 切片、函数、map 以及包含上述三种数据之一的结构体、数组均不能比较

d. 结构体

  • 结构体的声明与定义

    type Nat struct {
        n int
        d int
    }
    
    // or
    var a = Nat{
        1,
        2,
    }
    
    // or
    b := Nat{
        d: 2,
        n: 1,
    }
    

    var person struct {
    	name string
    	age  int
    }
    
  • 赋值

    person.name = "SRIGT"
    person.age = 18
    
  • 匿名结构体,如

    person := struct {
        name string
        age int
    }{
        name: "SRIGT",
        age: 18,
    }
    

0x03 语言特性

(1)函数

  • 功能抽象
  • 隐藏信息
  • 提高清晰度
  • 关注点分离
  • 代码复用

a. 函数声明

  • 一般声明

    func square(x int) int {
        return x * x
    }
    
    • 关键字func+函数名square+函数参数及参数类型x int+函数返回值int+函数体{}
  • 省略类型

    func add(x, y, z int) int {
    	return x + y + z
    }
    
  • 预留参数

    func function(x int, _ int)
    
  • 多返回值

    func add(x, y int) (int, int, int) {
    	return x, y, x+y
    }
    
  • 可变参数

    func add(numbers ...int) int {
    	var sum int
    	for _, value := range numbers {
    		sum += value
    	}
    	return sum
    }
    

b. 递归

  • 以阶乘计算为例

    func f(n int) int {
    	if n == 1 {
    		return 1
    	}
    	return n * f(n-1)
    }
    

c. 高阶函数

  • 函数作为参数,举例:设置函数可以计算给定的两个值之间所有整数的和、平方和

    func calculate(handle func(int) int, a, b int) int {
    	sum := 0
    	for i := a; i < b; i++ {
    		sum = sum + handle(i)
    	}
    	return sum
    }
    
    func main() {
    	fmt.Println(calculate(func(a int) int { return a }, 1, 5))
    	fmt.Println(calculate(func(a int) int { return a * a }, 1, 5))
    }
    
  • 函数作为返回值,举例:

    package main
    
    import (
    	"fmt"
    	"net/http"
    )
    
    func function(w http.ResponseWriter, r *http.Request) {
    	fmt.Fprintf(w, "Hello World")
    }
    
    func main() {
    	server := http.Server{Addr: "127.0.0.1:8080"}
    	http.HandleFunc("/", function)
    	server.ListenAndServe()
    }
    
  • 函数作为值,举例:

    func function1(i int) int { return i }
    func function2(i int) { function1(i) }
    
  • 闭包:可以让函数访问另外一个函数的参数和变量,举例:

    func function() {
    	x := 0
    	increment := func() int {
    		x++
    		return x
    	}
        fmt.Println(increment())	// output: 1
    }
    

(2)defer 函数

  • defer 函数一般用于资源的释放和异常的捕捉

  • 用法

    defer func(...){
        // code
    }()
    
  • 优势:资源释放更方便更容易

  • 特性

    • 延迟执行

    • 参数预计算

      func main() {
      	a := 1
      	defer func(b int) {
              fmt.Println(b)	// output: 2
      	}(a + 1)
      	a = 3
      }
      
    • LIFO 执行顺序

      for i := 1; i < 5; i++ {
      	defer fmt.Println(i)
      }
      
  • 最佳实践:服务器

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"time"
    )
    
    type MyHandler struct{}
    
    func (m MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    	before := time.Now()
    	defer func() {
    		after := time.Now()
    		fmt.Println(after.After(before))
    	}()
    	fmt.Fprintf(w, "Hello world")
    }
    
    func main() {
    	handler := MyHandler{}
    	server := http.Server{
    		Addr:    "127.0.0.1:8080",
    		Handler: recoverHandler(handler),
    	}
    	server.ListenAndServe()
    }
    
    func recoverHandler(next http.Handler) http.Handler {
    	fn := func(w http.ResponseWriter, r *http.Request) {
    		defer func() {
    			if err := recover(); err != nil {
    				log.Printf("panic: %+v", err)
    				http.Error(w, http.StatusText(500), 500)
    			}
    		}()
    		next.ServeHTTP(w, r)
    	}
    	return http.HandlerFunc(fn)
    }
    

(3)panic

  • 用法

    func executePanic() {
    	defer fmt.Println("defer function")
    	panic("panic function")
    	fmt.Println("println")
    }
    
    func main() {
    	executePanic()
    	fmt.Println("main println")
    }
    
  • recover 函数——捕获异常

    func executePanic() {
    	defer func() {
    		if err := recover(); err != nil {
    			fmt.Println("Panic: ", err)
    		}
    	}()
    	panic("panic function")
    	fmt.Println("println")
    }
    

(4)接口

a. 概述

  • 概述
    • 在计算机科学中,接口是一个共享边界,计算机系统的独立组件之间可以在该共享边界上交换信息,比如在软件,硬件,外围设备,人员之间
    • 在面向对象的编程语言中,接口是指相互独立的两个对象之间交流的方式
  • 特性
    • 可以为任何自定义的类型添加方法
    • 没有任何形式的基于类型的继承
    • 隐式声明
    • 实现扁平化、面向组合的设计模式

b. 使用

  • 声明与定义

    type InterfaceName interface {
    	function()
        // ...
    }
    
  • 实现

    // 声明接口
    type Shape interface {
    	perimeter() float64
    	area() float64
    }
    
    // 声明结构体
    type Rectangle struct {
    	a, b float64
    }
    
    // 周长计算
    func (r Rectangle) perimeter() float64 {
    	return (r.a + r.b) * 2
    }
    // 面积计算
    func (r Rectangle) area() float64 {
    	return r.a * r.b
    }
    
  • 动态调用

    func main() {
    	var shape Shape
    	shape = Rectangle{3, 4}
    	fmt.Println(shape.perimeter())
    	fmt.Println(shape.area())
    }
    
  • 类型断言

    func main() {
    	var shape Shape
    	shape = Rectangle{3, 4}
    	rect, ok := shape.(Rectangle)
    	if !ok {
    		fmt.Printf("s.(Rectangle) is not ok")
    	}
    	fmt.Println(rect.perimeter())
    	fmt.Println(rect.area())
    }
    
  • 空接口

    type Empty interface{}
    
    • 空接口的类型断言

      func MyPrintln(arg interface{}) {
          switch arg.(type) {
              case string:
              	fmt.Println("string")
              // ...
          }
      }
      

(5)反射

  • 概述

    • 在计算机科学中,反射是一种能力,可以让程序动态地检查或者修改它的结构
  • 使用场景

    • 检查函数的参数个数
    • 检查变量的类型
    • 根据方法名和函数名动态调用
    • 一般是框架或是基础服务的一部分
  • 使用方式

    • Value 与 Type

      import (
      	"fmt"
      	"reflect"
      )
      
      func main() {
      	var num float64 = 1.2345
      	fmt.Println("type: ", reflect.TypeOf(num))
      	fmt.Println("type is float 64: ", reflect.TypeOf(num).Kind() == reflect.Float64)
      	fmt.Println("value: ", reflect.ValueOf(num))
      }
      
      • 可以将 reflect.Value 看作反射的值,reflect.Type 看作反射的实际类型
    • 反射转换为接口

      func main() {
      	var num float64 = 1.2345
      
      	pointer := reflect.ValueOf(&num)
      	value := reflect.ValueOf(num)
      	fmt.Println(pointer.Interface())
      	fmt.Println(value.Interface())
      
      	convertPointer := pointer.Interface().(*float64)
      	convertValue := value.Interface().(float64)
      	fmt.Println(convertPointer)
      	fmt.Println(convertValue)
      }
      
      graph LR A(Interface)--TypeOf-->B(Type) A--ValueOf-->C(Value) C--Type-->B C--Interface-->A
    • 反射转换为实际值

      func main() {
      	a := reflect.ValueOf(123).Int()
      	fmt.Println(a)
      
      	b := reflect.ValueOf("String").String()
      	fmt.Println(b)
      
      	c := reflect.ValueOf(1.23).Float()
      	fmt.Println(c)
      
      	num := 123
      	d := reflect.ValueOf(&num).Elem().Int()
      	fmt.Println(d)
      }
      
    • Elem() 间接访问

      func main() {
      	var x int = 123
      	value := reflect.ValueOf(&x)
      	pointer := value.Elem()
      	pointer.SetInt(456)
      	fmt.Println(x)
      }
      
    • 修改反射的值

      func main() {
      	var num float64 = 1.23
      	pointer := reflect.ValueOf(&num)
      	newValue := pointer.Elem()
      	fmt.Println(newValue.Type())
      	fmt.Println(newValue.CanSet())
      
      	newValue.SetFloat(123)
      	fmt.Println(num)
      }
      
    • 结构体

      type User struct {
      	Id   int
      	Name string
      	Age  int
      }
      
      func (u User) ReflectCallFunc() {
      	fmt.Println("ReflectCallFunc")
      }
      
      func main() {
      	user := User{1, "SRIGT", 18}
      	DoFiledAndMethod(user)
      }
      
      // 通过接口来获取任意参数
      func DoFiledAndMethod(input interface{}) {
      	getType := reflect.TypeOf(input)
      	getValue := reflect.ValueOf(input)
      
          // 获取方法字段
          // 1. 获取 interface 的 reflect.Type,通过 NumField 遍历
          // 2. 通过 reflect.Type 的 Field 获取其他 Field
          // 3. 通过 Field 的 interface 获取对应的 Value
      	for i := 0; i < getType.NumField(); i++ {
      		field := getType.Field(i)
      		value := getValue.Field(i).Interface()
      		fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
      	}
      
      	for i := 0; i < getType.NumMethod(); i++ {
      		method := getType.Method(i)
      		fmt.Printf("%s: %v\n", method.Name, method.Type)
      	}
      
      	methodValue := getValue.MethodByName("ReflectCallFunc")
      	args := make([]reflect.Value, 0)
      	methodValue.Call(args)
      }
      
    • 函数

      type T struct {
      	A, b int
      }
      
      func (t T) AddSubThenScale(n int) (int, int) {
      	return n * (t.A + t.b), n * (t.A - t.b)
      }
      
      func main() {
      	t := T{3, 1}
      	vt := reflect.ValueOf(t)
      	vm := vt.MethodByName("AddSubThenScale")
      	results := vm.Call([]reflect.Value{reflect.ValueOf(3)})
      	fmt.Println(results[0].Int(), results[1].Int()) // 12 6
      
      	neg := func(x int) int {
      		return -x
      	}
      	vf := reflect.ValueOf(neg)
      	fmt.Println(vf.Call(results[:1])[0].Int()) // -12
      	fmt.Println(vf.Call([]reflect.Value{
      		vt.FieldByName("A"),
      	})[0].Int()) // -3
      }
      
    • 其他类型

      func main() {
      	ta := reflect.ArrayOf(5, reflect.TypeOf(123))
      	fmt.Println(ta)
      	tc := reflect.ChanOf(reflect.SendDir, ta)
      	fmt.Println(tc)
      	tp := reflect.PtrTo(ta)
      	fmt.Println(tp)
      	ts := reflect.SliceOf(tp)
      	fmt.Println(ts)
      	tm := reflect.MapOf(ta, tc)
      	fmt.Println(tm)
      	tf := reflect.FuncOf([]reflect.Type{ta}, []reflect.Type{tp, tc}, false)
      	fmt.Println(tf)
      	tt := reflect.StructOf([]reflect.StructField{ { Name: "Age", Type: reflect.TypeOf("abc") }, })
      	fmt.Println(tt)
      	fmt.Println(tt.NumField())
      }
      

0x04 并发编程

(1)协程

a. 概述

  • 进程、线程、协程

    • 进程 Process 是正在执行的程序的实例,包含程序代码、数据、文件和系统资源等;是操作系统资源分配的基本单位,每个进程都有各自独立的地址空间、文件描述符表等资源

    • 线程 Thread 是操作系统抽象出轻量级的资源;一个进程中包括多个线程,每个线程都共享进程的资源,并且都有各自独立的栈空间等资源

    • 协程构建于线程之上,即一个线程可以对应多个协程

      • 调度方式上,协程依附于线程进行调度
      • 上下文切换速度上,协程比线程更快
      • 调度策略上,协程一般是协作式,相对优于线程的抢占式
      • 栈的大小上,协程栈默认 2 KB,比线程要小
  • GMP 模型

    • 协程 G + 线程 M + 逻辑处理器 P

      flowchart LR subgraph 全局运行队列 A((G)) & B((G)) end subgraph 本地运行队列 C((G)) & D((G)) end 本地运行队列-->全局运行队列 -->本地运行队列 -->P -->M
  • 并发与并行

    • 并发发生在单核 CPU 中,通过上下文切换,进行事务切换,达成并发
    • 并行发生在多核 CPU 中,多个事务同时在不同核中运行,达成并行

b. 应用

  • 协程使用

    func main() {
    	links := []string{
    		"https://www.baidu.com/",
    		"https://www.bilibili.com/",
    	}
    	for _, link := range links {
    		go function(link)
    	}
    }
    
    func function(link string) {
    	_, err := http.Get(link)
    	if err != nil {
    		fmt.Println(link, " might be down")
    		return
    	}
    	fmt.Println(link, " is up")
    }
    
  • 主协程与子协程

    • 主协程在程序启动后,自动创建
    • 子协程创建用 go 关键字

(2)并发安全

  • 数据争用:指两个协程同时访问相同的内存空间,并且至少有一个是写操作的情况

  • race 工具

    • go test -race mypkg
    • go run -race mysrc.go
    • go build -race mycmd
    • go install -race mypkg
  • 并发安全的手段

    • 原子锁

      import "sync/atomic"
      
      var count int64 = 0
      
      func add() {
      	atomic.AddInt64(&count, 1)
      }
      func main() {
      	go add()
      	go add()
      }
      
    • 互斥锁

      import "sync"
      
      var count int64 = 0
      var m sync.Mutex
      
      func add() {
      	m.Lock()
      	count++
      	m.Unlock()
      }
      func main() {
      	go add()
      	go add()
      }
      
    • 读写锁

      import "sync"
      
      type Stat struct {
      	counters map[string]int64
      	mutex    sync.RWMutex
      }
      
      func (s *Stat) getCounters(name string) int64 {
      	s.mutex.RLock()
      	defer s.mutex.RUnlock()
      	return s.counters[name]
      }
      func (s *Stat) setCounters(name string) {
      	s.mutex.Lock()
      	defer s.mutex.Unlock()
      	s.counters[name]++
      }
      
    • WaitGroup

      import (
      	"fmt"
      	"sync"
      	"time"
      )
      
      func worker(id int) {
      	fmt.Printf("Worker %d Starting\n", id)
      	time.Sleep(time.Second)
      	fmt.Printf("Worker %d done\n", id)
      }
      func main() {
      	var wg sync.WaitGroup
      	for i := 1; i <= 5; i++ {
      		wg.Add(1)
      		i := i
      		go func() {
      			defer wg.Done()
      			worker(i)
      		}()
      	}
      	wg.Wait()
      }
      
    • Once

      import (
      	"sync"
      	"database/sql"
      	"fmt"
      )
      
      var once sync.Once
      
      func DbOnce() (*sql.DB, error) {
      	once.Do(func() {
      		fmt.Println("Am called")
      		db, dbErr := sql.Open("mysql", "root:test@tcp(127.0.0.1:3306)/test")
      		if dbErr != nil {
      			return
      		}
      		dbErr = db.Ping()
      	})
      	return db, dbErr
      }
      
    • Cond

(3)通道

  • 概述

    • 通道是一种特殊的数据类型,用于实现不同 goroutine 之间的通信和同步

      flowchart LR A(Main Routine)<-->Channel<-->B(Child go routine)
  • 使用

    • 通道声明与初始化

      chan int
      chan <- float64
      <-chan string
      
      var c = make(chan int)
      
    • 通道读写

      func main() {
      	var c = make(chan int)
      	go func() {
      		c <- 5
      	}()
      	fmt.Println(<-c)
      }
      
      • 返回值

        func main() {
        	var c = make(chan int)
        	go func() {
        		for {
        			data, ok := <-c
        			fmt.Println(data, ok)
        			if !ok {
        				break
        			}
        		}
        	}()
        }
        
    • 通道的堵塞

      func main() {
      	ch := make(chan int)
      	ch <- 1
      	fmt.Println(<-ch)
      }
      
    • 通道带缓冲

      func main() {
          // 创建一个带缓冲通道,容量为 2
      	ch := make(chan int, 2)
          // 向通道发送两个值
      	ch <- 1
      	ch <- 2
          // 从通道读取值
      	fmt.Println(<-ch)
      	fmt.Println(<-ch)
      }
      
    • 通道作为参数

      func worker(id int, c chan int) {
      	for n := range c {
      		fmt.Println(id, n)
      	}
      }
      
    • 通道作为返回值

      func workerCreate(id int) chan int {
      	c := make(chan int)
      	go worker(id, c)
      	return c
      }
      
    • 通道关闭

      func main() {
      	var c = make(chan int)
      	go func() {
      		data, ok := <-c
      		fmt.Println(data, ok)
      	}()
      	go func() {
      		data, ok := <-c
      		fmt.Println(data, ok)
      	}()
      	close(c)
      	time.Sleep(1 * time.Second)
      }
      
  • 最佳实践

    func main() {
    	links := []string{
    		"https://www.baidu.com/",
    		"https://www.bilibili.com/",
    	}
    	c := make(chan string)
    	for _, link := range links {
    		go function(link, c)
    	}
    	for link := range c {
    		link := link
    		go func(link string) {
    			time.Sleep(2 * time.Second)
    			function(link, c)
    		}(link)
    	}
    }
    
    func function(link string, c chan string) {
    	_, err := http.Get(link)
    	if err != nil {
    		fmt.Println(link, " might be down")
    		c <- link
    		return
    	}
    	fmt.Println(link, " is up")
    	c <- link
    }
    

(4)并发模式

  • Ping-Pong

    func main() {
    	var Ball int
    	table := make(chan int)
    	go player(table)
    	go player(table)
    	table <- Ball
    	time.Sleep(1 * time.Second)
    	<-table
    }
    func player(table chan int) {
    	for {
    		ball := <-table
    		ball++
    		time.Sleep(100 * time.Millisecond)
    		table <- ball
    	}
    }
    
  • Fan-in

    func main() {
    	ch := make(chan string)
    	go search(ch, "SRIGT")
    	for i := range ch {
    		fmt.Println(i)
    	}
    }
    func search(ch chan string, msg string) {
    	var i int
    	for {
             // 模拟找到关键字
    		ch <- fmt.Sprintf("get %s %d\n", msg, i)
    		i++
    		time.Sleep(100 * time.Millisecond)
    	}
    }
    
  • Fan-out

    func main() {
    	var wg sync.WaitGroup
    	wg.Add(36)
    	go pool(&wg, 36, 50)
    	wg.Wait()
    }
    func worker(tasksChan <-chan int, wg *sync.WaitGroup) {
    	defer wg.Done()
    	for {
    		task, ok := <-tasksChan
    		if !ok {
    			return
    		}
    		time.Sleep(time.Duration(task) * time.Millisecond)
    		fmt.Println(task)
    	}
    }
    func pool(wg *sync.WaitGroup, workers, tasks int) {
    	taskChan := make(chan int)
    	for i := 0; i < workers; i++ {
    		go worker(taskChan, wg)
    	}
    	for i := 0; i < tasks; i++ {
    		taskChan <- i
    	}
    	close(taskChan)
    }
    
  • Sub-worker

    const (
    	WORKER     = 5
    	SUBWORKERS = 3
    	TASKS      = 20
    	SUBTASKS   = 10
    )
    
    func main() {
    	var wg sync.WaitGroup
    	wg.Add(WORKER)
    	go pool(&wg, WORKER, TASKS)
    	wg.Wait()
    }
    func subworker(subtasks chan int) {
    	for {
    		task, ok := <-subtasks
    		if !ok {
    			return
    		}
    		time.Sleep(time.Duration(task) * time.Millisecond)
    		fmt.Println(task)
    	}
    }
    func worker(tasks <-chan int, wg *sync.WaitGroup) {
    	defer wg.Done()
    	for {
    		task, ok := <-tasks
    		if !ok {
    			return
    		}
    		subtasks := make(chan int)
    		for i := 0; i < SUBWORKERS; i++ {
    			go subworker(subtasks)
    		}
    		for i := 0; i < SUBTASKS; i++ {
    			taskI := task * i
    			subtasks <- taskI
    		}
    		close(subtasks)
    	}
    }
    func pool(wg *sync.WaitGroup, workers, tasks int) {
    	taskChan := make(chan int)
    	for i := 0; i < workers; i++ {
    		go worker(taskChan, wg)
    	}
    	for i := 0; i < tasks; i++ {
    		taskChan <- i
    	}
    	close(taskChan)
    }
    
  • Pipeline

    func main() {
    	multiply := func(value, multiplier int) int {
    		return value * multiplier
    	}
    	add := func(value, additive int) int {
    		return value + additive
    	}
    	ints := []int{1, 2, 3, 4}
    	for _, v := range ints {
    		fmt.Println(multiply(add(multiply(v, 2), 1), 2))
    	}
    }
    

0x05 项目组织

(1)依赖管理

  • 常见问题:多依赖、多重依赖、依赖冲突、循环依赖
  • 使用 Go Modules 替代 GOPATH
  • Go Modules 特点
    • 可以查找并下载所有依赖包
    • 更新依赖包
    • 本地文件管理依赖项、可重复构建
    • 解决包冲突、选择最兼容的包
    • 允许引用两个不同版本的第三方包

(2)Go Modules 实践

  • Go Modules 初始化:使用指令 go mod init [module name],执行后会自动生成 go.mod 文件
  • 引入第三方模块:在 main.go 中使用 import "[module name]" 语句导入其他第三方包
  • 下载第三方模块:使用命令 go get [module name] 下载第三方包
  • 使用第三方模块:在其他代码中直接调用
  • 手动更新第三方模块:在 go.mod 文件中修改包的版本,并使用命令 go mod download [module name]
  • replace 指令:替代,在 go.mod 中输入 replace [module name] v1.0.1 => [module name] v1.0.0
  • retract 指令:撤回,在 go.mod 中输入 retract [module name] v1.0.0

0x06 生态

(1)测试

  • 单元测试(unit testing)

    • 单元测试是对单个函数或代码块进行测试的过程,它测试函数或代码块的输入和输出是否符合预期

    • Go语言中内置的 testing 包提供了基本的单元测试支持

    • 举例

      • add.go

        package add
        
        func Add(a, b int) int {
        	return a + b
        }
        
      • add_test.go

        package add
        
        import "testing"
        
        func TestAdd(t *testing.T) {
        	sum := Add(1, 2)
        	if sum == 3 {
        		t.Log("Successed to add")
        	} else {
        		t.Fatal("Failed to add")
        	}
        }
        
  • 表格测试(table-driven testing)

    • 表格驱动测试使用循环遍历的方式遍历多组测试数据
    • 使用表格驱动测试可以方便地执行一组或多组测试,同时可以减少测试代码的冗余
  • 子测试(subtests)

    • 常与表格测试结合 t.Run()
  • 基准测试(benchmark testing)

    • 基准测试用于测量程序在已知负载下的性能,其他目的在于确定某个程序、算法或者硬件系统的
      性能指标

    • 举例:

      type Sumifier interface{ Add(a, b int) int }
      
      type Sumer struct{ id int32 }
      
      func (math Sumer) Add(a, b int) int { return a + b }
      
      type SumerPointer struct{ id int32 }
      
      func (math SumerPointer) Add(a, b int) int { return a + b }
      
      func BenchmarkDirect(b *testing.B) {
      	adder := Sumer{id: 6754}
      	b.ResetTimer()
      	for i := 0; i < b.N; i++ {
      		adder.Add(10, 12)
      	}
      }
      
      func BenchmarkInterface(b *testing.B) {
      	adder := Sumer{id: 6754}
      	b.ResetTimer()
      	for i := 0; i < b.N; i++ {
      		Sumifier(adder).Add(10, 12)
      	}
      }
      
      func BenchmarkInterfacePointer(b *testing.B) {
      	adder := &SumerPointer{id: 6754}
      	b.ResetTimer()
      	for i := 0; i < b.N; i++ {
      		Sumifier(adder).Add(10, 12)
      	}
      }
      
  • 代码覆盖率测试(code coverage testing)

    • 单元覆盖率:go test -cover ./name
      • 可视化方法浏览:
        • go test -coverprofile=opt.out
        • go tool cover -html=opt.out
  • 模糊测试(fuzz testing)

    • 模糊测试是一种黑盒测试技术,它会使用各种自动化工具生成大量随机的输入数据,然后将这些输入数据传递给被测程序进行测试,以此来检测被测程序中是否存在潜在的漏洞或错误

(2)调试

  • 常见的调试方法和技术
    • 打印调试信息
    • 调试器
    • 远程调试
    • 性能分析工具
      • PPROF:Go 语言的性能分析利器,用于对指标或特征进行分析,可以帮助定位程序中的错误并优化
      • TRACE:可以捕获一段时间内程序的运行状态和事件,并以可视化的方式呈现出来
  • Delve 调试器
    • 安装 Delve:go install github.com/go-delve/delve/cmd/dlv@latest
    • 编译程序:go build -gcflags=all="-N -l" main.go
  • GoLand 进行本地 / 远程调试

(3)标准库

  • Go标准库由官方维护,涵盖了大多数通用场景和现代开发所需的核心部分,旨在为Go语言提供丰富而有力的基础功能和工具

  • 字符串和文本处理

    名称 说明
    fmt 文本格式化函数
    strings 字符串操作
    strconv 类型转换
    bytes 字节切片操作
    unicode Unicode 编解码和分类
    regexp 正则表达式支持
    html 操作和生成 HTML 文件
    index 文本索引相关的操作
  • 数学与加密

    名称 说明
    math 数学函数
    crypto 密码学
    hash 哈希函数
    compress gzip 等压缩算法
  • IO 和文件处理

    名称 说明
    io 输入输出原语
    bufio 带缓存的输入输出
    ioutil IO 实用函数
    os 操作系统和文件系统操作
    path/filepath 文件路径操作
    archive 压缩和解压缩文件
  • 网络与协议

    名称 说明
    net 网络 IO 函数
    net/http HTTP 客户端和服务端
    net/rpc RPC 客户端和服务端
    encoding JSON 等编解码
  • 并发和同步

    名称 说明
    sync 同步原语
    context 控制协程退出
  • 数据结构和算法

    名称 说明
    container 常用的容器和数据结构
    sort 排序
    math/rand 随机数生成
  • 工具

    名称 说明
    testing 代码测试
    log 日志记录
    time 时间和计时器
    pprof 性能分析
    trace 追踪程序执行
    go/ast 将 Go 源码解析为抽象语法树
    errors 错误处理
    unsafe 指针操作与访问未导出的成员
    runtime 运行时
    debug 调试信息
    flag 命令行选项解析
    image 支持各种图像格式和编解码
    reflect 反射操作
    database 数据库驱动

(4)第三方库

  • Web 框架:Gin、Echo、Beego
  • 数据库:GORM、Xorm、mongo-go-driver、gocql
  • 缓存:go-redis、goCache、groupcache
  • 测试:testify、gomonkey
  • 配置:Cobra、viper、configor
  • 图片处理:imaging、gocv、govatar
  • 序列化:protobuf、go-json、msgpack
  • 微服务:go-micro、grpc-go、go-zero、go-kit
  • 网络编程:websocket、mqtt、rpcx

(5)泛型

a. 概述

  • 泛型编程的核心思想是从具体的、高效的运算中抽象出通用的运算,这些运算可以适用于不同形式的数据,从而能够适用于各种各样的场景
  • Go 1.18 引入泛型
  • 特性
    • 只存在于编译时
    • 避免代码的重复,更简洁的代码和 API
    • 编译速度略微下降
    • 有类型的约束
    • 拓展了接口的能力

b. 使用

  • 泛型声明

    // 切片
    type Slice1 [T int|float64|string] []T
    
    type Slice2 []int
    type Slice3 []float64
    type Slice4 []string
    
    // Map
    type Map1 [KEY int | string, VALUE string | float64] map[KEY]VALUE
    
    type Map2 map[int]string
    type Map3 map[int]float64
    type Map4 map[string]string
    
    // 结构体
    type Struct1 [T string|int|float64] struct {
    	Title string
    	Content T
    }
    
    type Struct2 struct {
    	Title string
    	Content string
    }
    type Struct3 struct {
    	Title string
    	Content int
    }
    type Struct4 struct {
    	Title string
    	Content float64
    }
    
  • 泛型约束

    • 约束基于接口,是类型的集合

    • ~T 表示一个类型集,包括所有基础类型为 T 的类型

    • 并集:

      type Ordered interface {
          Integer | Float | ~string
      }
      
    • 交集:

      type m interface {
          int | ~float64
          float 64
      }
      
    • 预置类型

      • any:预定义类型,空接口的别名
      • comparable:预定义类型(==!==
      • constrains.Ordered:预定义类型(<<=>=>
  • 泛型实例化

    • 类型

      // 切片
      type Slice1 [T int|float64|string] []T
      var MySlice Slice1[int] = []int{1, 2, 3}
      
      // Map
      type Map1 [KEY int | string, VALUE string | float64] map[KEY]VALUE
      var MyMap Map1[int, string] = map[int]string{
          1: "hello",
          2: "world",
      }
      // 结构体
      type Struct1 [T string|int|float64] struct {
      	Title string
      	Content T
      }
      var MyStruct = Struct1[string]{
      	Title "hello"
      	Content "world"
      }
      
    • 函数

      func function[V comparable](vs ...V) bool {
      	if len(vs) == 0 {
      		return true
      	}
      	v := vs[0]
      	for _, x := range vs[1:] {
      		if v != x {
      			return false
      		}
      	}
      	return true
      }
      
      func main() {
      	fmt.Println(function[string]("function", "Function"))
      	fmt.Println(function[int](123, 456))
      	fmt.Println(function[bool]())
      }
      
      • 自动类型推断

        func main() {
        	fmt.Println(function("function", "Function"))
        }
        

-End-

posted @ 2023-08-04 11:34  SRIGT  阅读(21)  评论(0编辑  收藏  举报