go语言进阶知识&依赖管理 | 青训营

97 阅读1分钟

进阶知识

go并发

并发

并发分为单核并发和多核并发

单核并发是通过时间片轮转来实现的,多核并发则相对简单一些

多线程程序在单核CPU上运行

image.png

多线程程序在多核cpu上运行

image.png

go语言的机制,可以使go充分发挥多核优势,高效运行

Goroutine(协程)

协程是比线程级别小的状态,就像进程上可以有多个线程线程上可以有多个协程,就像c/c++在多线程上实现高并发,而go语言则在多协程下实现高并发

image.png

协程:用户态,轻量级线程,栈KB级别

线程:内核态,线程跑多个协程,栈MB级别

go语言是通过协程来实现高并发,由于协程的这种机制,也使得go语言更适合高并发编程

创建协程

go语言创建协程格式

go 协程函数

示例代码

package main
​
import (
    "encoding/json"
    "errors"
    "fmt"
    "math"
    "os"
    "os/exec"
    "strconv"
    "strings"
    "time"
)
​
func hello(i int) {
    fmt.Println("hello goroutine : " + fmt.Sprint(i))
}
​
func main() {
    for i := 0; i < 5; i++ {
        go func(j int) { // 创建协程和函数体
            hello(j)
        }(i)
    }
    time.Sleep(time.Second)
}

运行结果

image.png

从运行结果中体现了并发现,也可以看出高并发协程执行顺序的不确定性

协程间通信

有了多协程,那一定就会有协程间的通信机制

image.png

go提倡通过通信共享内存而不是通过共享内存而实现通信,也是为了避免多协程下访问临界资源而带来不必要的麻烦

Channel(通道)

image.png

go的通道通信与线程间的管道通信还是比较相似的

创建通道

make(chan 元素类型,[缓冲大小])

无缓冲通道 make(char int)

有缓冲通道 make(char int,2)

示例代码

创建两个协程,其中A子协程发送0~9数字、B子协程计算输入数字的平方,主协程输出最后的平方数

package main
​
import (
    "encoding/json"
    "errors"
    "fmt"
    "math"
    "os"
    "os/exec"
    "strconv"
    "strings"
    "time"
)
​
func main() {
    src := make(chan int)
    dest := make(chan int, 3)
    go func() {
        defer close(src)
        for i := 0; i < 10; i++ {
            src <- i
        }
    }()
    go func() {
        defer close(dest)
        for i := range src {
            dest <- i * i
        }
    }()
    for i := range dest {
        fmt.Println(i)
    }
}

运行结果

image.png

从运行结果可以看出,通道满足先进先出

锁机制

在多协程中,对非线程安全的临界资源访问,可能会出现非预期的结果,所以就需要锁机制来实现同步或互斥操作

示例代码

package mainimport (
    "encoding/json"
    "errors"
    "fmt"
    "math"
    "os"
    "os/exec"
    "strconv"
    "strings"
    "sync"
    "time"
)
​
var (
    x    int64
    lock sync.Mutex
)
​
func addWithLock() {
    for i := 0; i < 2000; i++ {
        lock.Lock()
        x += 1
        lock.Unlock()
    }
}
​
func addWithnotLock() {
    for i := 0; i < 2000; i++ {
        x += 1
    }
}
​
func main() {
    x = 0
    for i := 0; i < 5; i++ {
        go addWithnotLock()
    }
    time.Sleep(time.Second)
    fmt.Println("WithnotLock:", x)
    x = 0
    for i := 0; i < 5; i++ {
        go addWithLock()
    }
    time.Sleep(time.Second)
    fmt.Println("WithLock:", x)
}

运行结果

image.png

WaitGroup(P/V操作、计数器)

go语言中也有pv操作,用来等待子协程结束或进行同步或互斥操作

WaitGroup提供了三个方法用来对计数器进行操作

Add(delta int) // 初始化计数器 计数器 + delta
Done() // 计数器-1
Wait() // 阻塞直到计数器为0

示例代码

go并发的示例代码进行修改

package main
​
import (
    "encoding/json"
    "errors"
    "fmt"
    "math"
    "os"
    "os/exec"
    "strconv"
    "strings"
    "sync"
    "time"
)
​
func main() {
    var wg sync.WaitGroup // 声明计数器变量
    wg.Add(5) // 计数器初始化
    for i := 0; i < 5; i++ {
        go func(j int) {
            defer wg.Done() // 计数器-1
            hello(j)
        }(i)
    }
    wg.Wait() // 阻塞等待计数器值为0
}

运行结果

image.png

依赖管理

背景

在项目开发中,不可能是从0开始一行行的代码敲,都是基于已经开发好的代码块进行编程,这样也能大大增加开发效率,这也是模块化编程的理念

image.png

工程项目不可能基于标准库进行编码

管理依赖库

Go依赖管理演进

image.png

不同环境(项目)依赖的版本不同

控制依赖库的版本

GOPATH

GOPATH为go语言的环境变量,对程序依赖,和包下载提供路径

image.png

image.png

项目代码直接依赖src下的代码

go get下载最新版本的包到src目录下

GOPATH-弊端

image.png

场景:A和B依赖于某一package的不同版本

问题:无法实现package的多版本控制

Go Vendor

项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor

依赖寻址方式:vendor => GOPATH

image.png

通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题

Go Vendor-弊端

image.png

问题:

无法控制依赖的版本

更新项目又可能出现依赖冲突,导致编译出错

Go Module

通过go.mod文件管理依赖包版本

通过go get/go mod指令工具管理依赖包

最终目标:定义版本规则和管理项目依赖关系

依赖管理三要素

1. 设备文件,描述依赖   go.mod
2. 中心仓库管理依赖库   Proxy
1. 本地工具                       go get/mod

依赖配置-go.mod

image.png

依赖标识:[Module Path] [Version/Pseudo-version]

依赖配置-version

语义化版本

MAJOR.{MAJOR}.{MINOR}.${PATCH}

v1.3.0

v2.3.0

基于commit伪版本

vX.0.0-yyyymmddhhmmss-abcdefgh1234

v0.0.0-20220401081311-c35fg59326f7

v1.0.0-20201130134442-10cb98267c6c

依赖配置-indirect

image.png

A→B→C A→B 直接依赖 A→C间接依赖

依赖配置-incompatible

image.png

主版本2+模块会在模块路径添加/vN后缀

对于没有go.mod文件并且主版本2+的依赖,会+incompatible

依赖配置-依赖图

image.png

如果一个程序,对一个模块有多个版本依赖,则选择最低可以支持的版本进行依赖

依赖分发-回源

image.png

1.无法保证构建稳定性

增加/修改/删除软件版本

2.无法保证依赖可用性

删除软件

3.增加第三方压力

代码托管平台负载问题

依赖分发-Proxy

image.png

采用中间件的方式,来保证程序依赖的稳定性,在Proxy中会缓存和管理Developer所需要的依赖

依赖分发-变量GOPROXY

image.png

GOPROXY="proxy1.cn,https://proxy2.cn,…"

服务站点URL列表,"direct"表示源站

工具-go get

image.png

工具-go mod

image.png