协程
协程:用户态,轻量级线程,栈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和传统的并发编程模型
提倡通过通信共享内存(通道)而不是通过共享内存而实现通信
因为通信共享内存的方式会发生数据竞态,影响程序性能。
channel
make(chan 元素类型,[缓冲大小])
- 无缓冲通道 make(chan int) 又称同步通道
- 有缓冲通道 make(chan int,2)
// 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) // 确保协程执行完之前,主协程没有退出
}
注意点:
-
在使用完channel之后,我们可以关闭channel。如果不关闭channel,会导致以下问题:
1)接收者可能会一直等待channel中的值,导致程序死锁
2)发送者如果没有及时关闭channel,会浪费内存资源
因此,建议在不需要向channel写入数据时,手动关闭channel。关闭channel可以使用close(channel)函数
依赖管理
GOPATH---->>Go Vendor---->>Go Module
- 不同环境(项目)依赖的版本不同
- 控制依赖库的版本
GOPATH
- 环境变量 $GOPATH
- 项目代码直接依赖src下的代码
- go get 下载最新版本的包到src目录下
Go Vendor
- 项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor
- 依赖寻址方式:vendor => GOPATH
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。
问题
- 无法控制依赖的版本。
- 更新项目又可能出现依赖冲突,导致编译错误。
Go Module
- 通过go.mod文件管理依赖包版本
- 通过go get/go mod指令工具管理依赖包
终极目标:定义版本规则和管理项目依赖关系
依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
关键字
indirect
graph LR;
A-->B-->C
- A->B 直接依赖
- A->C 间接依赖 // indirect
incompatible
- 主版本2+模块会在模块路径增加/vN后缀
- 对于没有go.mod文件并主版本2+的依赖,会+incompatible
如果一个库的包很多,如果想单独引用某个包的话,需要在其目录下新建go.mod文件
依赖图
依赖分发
回源
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 增加需要的依赖,删除不需要的依赖]