go并发与依赖管理 | 青训营笔记

69 阅读1分钟

协程

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

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

func hello(i int) {
	println("hello goroutine " + fmt.Sprint(i))
}

func HelloGoGoRoutine() {
	for i := 0; i < 5; i++ {
		go func(j int) {
			hello(j)
		}(i)
	}
	time.Sleep(time.Second) // 确保协程执行完之前,主协程没有退出
}

并发

并发安全是指在多线程或多协程并发访问时,系统或程序仍然能够正常工作而不会出现数据竞争、死锁等问题。通常在编写并发程序时需要考虑并发安全问题,采取相应的并发措施来保证使用程序的正确性和稳定性。比如,使用互斥锁、读写锁等机制来保护共享变量的访问,避免多个协程同时修改同一个变量,从而造成数据不一致、丢失等问题。

或者采用CSP并发网络编程模型

CSP (Communicating Sequential Processes)

下面是CSP和传统的并发编程模型

byteDance2-1.png

提倡通过通信共享内存(通道)而不是通过共享内存而实现通信

因为通信共享内存的方式会发生数据竞态,影响程序性能。

channel

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

  • 无缓冲通道 make(chan int) 又称同步通道
  • 有缓冲通道 make(chan int,2)

byteDance2-2.png

// CSP模型例子
func CalSquare() {
	src := make(chan int)
	dst := make(chan int, 3)
	go func() {
		defer close(src)
		for i := 0; i < 10; i++ {
			src <- i
		}
	}()

	go func() {
		defer close(dst)
        // range遍历channel时会阻塞等待直到数据发送完或者channel被关闭
		for i := range src {
			dst <- i * i
		}
	}()

	for i := range dst {
		println(i)
	}
}

sync.WaitGroup

sync.WaitGroup是Go语言标准库sync包中的一个结构体,用于等待一组goroutine的结束。它提供了三个方法:Add、Done和Wait。

在使用WaitGroup的时候,首先需要使用Add方法来设置等待的goroutine数量,然后每个goroutine结束时调用Done方法来标记自己已经结束了,最后在主goroutine中调用Wait方法来等待所有的goroutine结束。

WaitGroup的主要作用是同步多个goroutine之间的执行,等待所有goroutine都完成任务后再进行后续操作。通常情况下,WaitGroup和channel联合使用可以实现更为复杂的并发控制,例如控制同时运行的goroutine数量等。

func HelloGoGoRoutine() {
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < 5; i++ {
		go func(j int) {
			defer wg.Done()
			hello(j)
		}(i)
	}
	wg.Wait()
	//time.Sleep(time.Second) // 确保协程执行完之前,主协程没有退出
}

注意点:

  1. 在使用完channel之后,我们可以关闭channel。如果不关闭channel,会导致以下问题:

    1)接收者可能会一直等待channel中的值,导致程序死锁

    2)发送者如果没有及时关闭channel,会浪费内存资源

    因此,建议在不需要向channel写入数据时,手动关闭channel。关闭channel可以使用close(channel)函数

依赖管理

GOPATH---->>Go Vendor---->>Go Module

  • 不同环境(项目)依赖的版本不同
  • 控制依赖库的版本

GOPATH

byteDance2-3.png

  • 环境变量 $GOPATH
  • 项目代码直接依赖src下的代码
  • go get 下载最新版本的包到src目录下

byteDance2-4.png

Go Vendor

  • 项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor
  • 依赖寻址方式:vendor => GOPATH

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

byteDance2-5.png

问题

  • 无法控制依赖的版本。
  • 更新项目又可能出现依赖冲突,导致编译错误。

Go Module

  • 通过go.mod文件管理依赖包版本
  • 通过go get/go mod指令工具管理依赖包

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

依赖管理三要素

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

byteDance2-6.png

关键字

indirect

graph LR;
A-->B-->C
  • A->B 直接依赖
  • A->C 间接依赖 // indirect

incompatible

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

如果一个库的包很多,如果想单独引用某个包的话,需要在其目录下新建go.mod文件

byteDance2-7.png

依赖图

byteDance2-8.png

依赖分发

回源

graph TD;
A[Github]-->B[Developer]
C[SVN]-->B
D[...]-->B

如果直接从代码托管平台中下载代码会出现以下问题:

  • 无法保证构建稳定性:增加/修改/删除软件版本
  • 无法保证依赖可用性:删除软件
  • 增加第三方压力:代码托管平台负载问题

Proxy

graph TD;
A[Github]-->B[Proxy]
C[SVN]-->B
D[...]-->B
B-->E[Developer]

Proxy缓存软件内容

变量GPROXY

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

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

graph LR;
a[Proxy1]-->b[Proxy2]-->c[Direct]

工具

graph LR;
a[go get example.org/pkg]-->b[update 默认]
a-->c[none 删除依赖]
a-->d[v1.1.2 tag版本,语义版本]
a-->e[master 分支最新commit]
graph LR;
a[go mod]-->b[init 初始化go.mod文件]
a-->c[download 下载模块到本地缓存]
a-->d[tidy 增加需要的依赖,删除不需要的依赖]