goroutine与依赖管理 | 青训营笔记

133 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天.

一.GOROUTINE

1.并行与并发

  1. 并发:多线程程序在一个核的cpu上运行.
  2. 并行:多线程程序在多个核的cpu上运行.
  3. 一半的并发指广义上的并发,实际中,并行可以理解为实现并发的一个手段.

2.协程goroutine

1.协程:用户态,轻量级线程,栈KB级别
2.线程:内核态,线程跑多个协程,栈MB级别

3.CSP(Communicating Sequential Processes)

gorountine提倡通过通信共享内存而不是通过共享内存而实现通信.
image.png
go也保留通过共享内存实现通信的机制.
image.png

4.Channel

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

func main() {
   ch := make(chan string, 6)
   go func(ch chan string) {
      ch <- fmt.Sprintf("早上好")
   }(ch)
   fmt.Println(<-ch)
}

1.无缓冲通道: 会使发送的goroutine和接收的goroutine同步化. 2.有缓冲通道:通道的缓冲大小,代表通道的容量,如果容量满时不使用通道,那么goroutine会阻塞起来.

func producer(header string, channel chan<- string) {
   for {
      fmt.Println("ing...")
      channel <- fmt.Sprintf("%s: %v", header, rand.Int31())
   }
}
func customer(channel <-chan string) {
   for {
      //不使用chan,让chan的缓冲占满
   }
}
func main() {
   // 创建一个字符串类型的通道
   channel := make(chan string, 2)
   // 创建producer()函数的并发goroutine
   go producer("producer", channel)
   // 数据消费函数
   customer(channel)
   defer close(channel)
}

输出两个ing...后程序阻塞住了.

5.并发安全Lock

1.sync.Mutex

var (
   x     int64
   lock  sync.Mutex   //互斥锁
   lock1 sync.RWMutex //读写锁
)
func Add() {
   x = 0
   for i := 0; i < 5; i++ {
      go addWithOutLock()
   }
   time.Sleep(time.Second)
   println("WithOutLock:", x)
   x = 0
   for i := 0; i < 5; i++ {
      go addWithLock()
   }
   time.Sleep(time.Second)
   println("WithLock:", x)
}

func addWithLock() {
   for i := 0; i < 2000; i++ {
      lock.Lock()
      defer lock.Unlock()
      x++
   }
}

func addWithOutLock() {
   for i := 0; i < 2000; i++ {
      x++
   }
}

sync.Mutex是互斥锁,是对传统并发程序对共享资源进行控制访问的主要手段.这是go对于共享内存实现通信的机制.
sync.Mutex只要声明就可以使用.对于不使用通道的goroutine,不加锁的结果是不确定的,这是危险的.其次,- 同级别互斥锁不能嵌套使用,这是很值得注意的.除此外还有sync.RWMutex读写锁等.

2.sync.WaitGroup计数器

add(n)操作会使得计数器+n,done()会使得计数器-1,wait()表示等待计数器归0.
func addWithLock() {
   for i := 0; i < 2000; i++ {
      lock2.Add(1)
      x++
      lock2.Done()
   }
}
for i := 0; i < 5; i++ {
   go addWithOutLock()
}
lock2.Wait()

使用sync.WaitGroup避免使用time.Sleep(),使用time.Sleep()其实并不稳定,你不能保证goroutine一定会在某个时间内完成.

二.依赖管理

1.go依赖管理演进

GOPATH->Go Vendor->Go Module

2.GOPATH

环境变量$GOPATH

2.1 结构

包括bin,pkg,src
bin是项目编译的二进制文件
pkg是项目编译的中间产物,加速编译
src是项目源代码

2.2 项目代码依赖src下的代码

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

2.3 弊端

1.无法实现package的多版本控制
比如: a和b依赖于某一个package的不同版本,a和b就不能同时构建成功.

3.Go Vendor

3.1 结构

1.比GOPATH增加了vendor文件,所有依赖包副本形式放在vendor内.
2.依赖寻址方式: vendor => GOPATH

3.2 好处

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

3.3 缺点

1.无法控制依赖的版本
2.更新项目有可能出现依赖冲突,导致编译出错.
比如:
项目a依赖于项目b和c,项目b依赖于项目D的v1版本,项目c依赖于项目D的v2版本.

4.Go Module

1.变化

1.通过go.mod文件管理里依赖包版本.
2.通过go get/go mod指令根据管理依赖包.

2,依赖三要素

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

3.go.mod配置

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

4.依赖配置-version

1.语义化版本
MAJOR.{MAJOR}.{MINOR}.${PATCH}
如:v1.3.0
2.基于commit伪版本
vX.0.0-yyyymmddhhmmss-abcdefgh1234
如:v1.0.0-20201130134442-10cb98267c6c

5.依赖配置-indirect

A->B->C => A->B 直接依赖 A->C 间接依赖
间接依赖会用//indirect指出

6.依赖配置-incompatible

1.主版本2+模块会在模块路径增加/vN后缀
2.对于没有go.mod文件并且主版本2+的依赖,会+incompatible

7.依赖版本

go在对于两个不同版本的项目,是使用最低的兼容版本.

5.依赖分发

1.依赖分发-回源

直接使用版本管理依赖仓库的问题:
1.无法保证构建的稳定性:增加/修改/删除软件版本
2.无法保证依赖可用性:删除软件
3.增加第三方压力:代码托管平台负载问题

2.依赖分发-Proxy

Proxy会缓存原仓库的软件内容.
image.png

3.依赖分发-变量 GOPROXY

GOPROXY="proxy1.cn ,proxy2.cn ,direct"
服务站点URL列表,"direct"表示源站
依赖寻找模式:proxy1->proxy2->direct