03 Go 语言工程进阶(上)| 青训营笔记

53 阅读2分钟

这是我参与「第五届青训营」伴学笔记创作活动的第三天。
这一篇是之前学习过的课程笔记总结。记录自己学习过程。

知识要点

  1. 语言进阶
  2. 依赖管理
  3. 测试(中篇和下篇继续)
  4. 项目实战

知识点详解

语言进阶

这里主要讲解并发编程:

  • 并发 vs 并行 image.png
  • 协程(支持并行的关键, Goroutine)
    协程:用户态,轻量级线程,栈KB级别。->Go语言用户操作
    线程:内核态,县城跑多个协程,栈MB级别。->昂贵的系统资源,线程可以并发运行多个协程。
  • 如何开启一个协程
    案例:快速打印 hello goroutine (0-4) 不要求打印顺序
    代码如下,注意以下几点:
    1)加上go关键字(为代码创建协程)
    2)使用sleep进行阻塞(保证子协程执行完之前线程不退出)
    package main
    
    import (
            "fmt"
            "time"
    )
    
    func hello(i int) {
            println("hello goroutine:" + fmt.Sprint(i))
    }
    
    func HelloGoRoutine() {
            for i := 0; i < 5; i++ {
                    go func(j int) {
                            hello(j)
                    }(i)
            }
            time.Sleep(time.Second)
    }
    
    func main() {
            HelloGoRoutine()
    }
    
    其中一次执行结果如下:
    image.png
  • 协程交互
    go语言提倡通信共享内存,而不是共享内存进行通信
    image.png
    前者主要使用队列,后者需要加锁实现
    • 具体实现->Channel
      make(chan 元素类型,[缓存大小])
      分为两种:
      无缓存通道 如make(chan,int)
      有缓存通道 如make(chan,int,2)
      两者区别如下图所示:
      image.png
      一个具体使用案例:
      要求如下
      1. A子协程:发送数字0-9,
      2. B子协程计算数字平方,
      3. 最后主协程输出最后平方数
    代码和执行结果如下所示:
    注意:
    1)考虑消费者B消费速度可能比较慢,生产者A逻辑简单。添加带缓冲的channel减少两者速度上的不匹配
    2)defer 延迟资源关闭
    package main
    
    func CalSquare() {
            src := make(chan int)
            dest := make(chan int, 3)
    
            // A
            go func() {
                    defer close(src)
                    for i := 0; i < 10; i++ {
                            src <- i
                    }
            }()
            // B
            go func() {
                    defer close(dest)
                    for i := range src {
                            dest <- i * i
                    }
            }()
            for i := range dest {
                    println(i)
            }
    }
    
    func main() {
            CalSquare()
    }
    
    
    image.png
  • 并发安全 Lock
    共享内存支持通信->多个协程同时对一块内存进行操作。
    案例目标
    对变量执行2000次+1操作,5个协程并发
    主要区别加锁 (使用sync.Mutex实现) 和不加锁的区别:
    package main
    
    import (
            "sync"
            "time"
    )
    
    var (
            x    int64
            lock sync.Mutex
    )
    
    func addWithLock() {
            for i := 0; i < 2000; i++ {
                    lock.Lock()
                    x += 1
                    lock.Unlock()
            }
    }
    
    func addWithOutLock() {
            for i := 0; i < 2000; i++ {
                    x += 1
            }
    }
    
    func Add() {
            x = 0
            for i := 0; i < 5; i++ {
                    go addWithOutLock()
            }
            time.Sleep(time.Second)
            println("Without lock:", x)
    
            x = 0
            for i := 0; i < 5; i++ {
                    go addWithLock()
            }
            time.Sleep(time.Second)
            println("With lock:", x)
    }
    func main() {
            Add()
    }
    
    其中一次执行结果如下:
    image.png
  • WaitGroup
    更好的执行阻塞的方法,而不是使用time.Sleep image.png
    更改快速打印hello goroutine如下:
    package main
    
    import (
            "fmt"
            "sync"
    )
    
    func hello(i int) {
            println("hello goroutine:" + fmt.Sprint(i))
    }
    
    func HelloGoRoutine() {
            var wg sync.WaitGroup // 定义一个计数器
            for i := 0; i < 5; i++ {
                    go func(j int) {
                            defer wg.Done()
                            hello(j)
                    }(i)
            }
            wg.Wait()
    }
    
    func main() {
            HelloGoRoutine()
    }
    

依赖管理

  • 背景:需要使用他人依赖包可以加快开发效率 image.png
  • Go 依赖管理演进 image.png
    目的:不同环境(项目)依赖版本不同,控制依赖库的版本。
  • GoPath image.png
    问题如下: 不能实现package的多版本控制 image.png
  • 改进 Govender image.png
    问题如下 image.png
    1)无法控制依赖的版本
    2)更新项目有可能出现依赖冲突,导致编译出错
  • Go Module
    方式:
    通过go.mod 文件管理依赖包版本 通过go get/go mod 指令工具管理依赖包
    **终极目标:**定义版本规则和管理项目依赖关系
  • 依赖管理三要素
  1. 配置文件,表述依赖 -> go.mod
  2. 中心残酷管理依赖库 -> Proxy
  3. 本地工具 go get/mod
  • 实操案例 image.png
    image.png
    注释1:
    major->大功能更新,不同major可以看作代码隔离
    minor->增加新增功能,要求同一个major之间兼容
    PATH->主要修复bug等功能
    注释2:
    1.版本前缀(和语义化版本一致)
    2.时间戳
    3.提交commit哈希码
  • 一些关键字
    indirect(没有直接依赖)
    incompatible(对于没有go.mod文件但是主版本为2+依赖,会加上这个关键字)
  • 依赖图
    答案为B(选择最低兼容版本) image.png (后续在Go 语言工程进阶(中)继续补充)

参考资料和补充阅读

  1. 课程地址:Go 语言进阶与依赖管理 - 掘金 (juejin.cn)