go语言进阶与依赖管理
1.语言进阶(并发编程)
1.0 并发与并行
并发:多线程程序在单核cpu运行(主要是调度)
并行:多线程程序在多核cpu运行
go语言为并发而生。
1.1 协程-go routine
实现高并发
协程-用户态,轻量级,栈MB级别
线程-内核态,线程跑多个协程,栈KB级别
package main
import (
"fmt"
"time"
)
func hello(i int) {
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) //保证子协程运行完之前主协程不退出
}
1.2 缓冲区-channel
通过通信共享内存
// 这个程序就是创建了两个channel,一个是src,一个是dest,src写入0-9的数字,dest读取src中的数字并将它的平方写入dest中,最后遍历dest并输出。
package main
import "fmt"
func CalSquare() {
src := make(chan int) //src无缓冲队列
dest := make(chan int, 3) //dest有缓冲队列(3)
//缓冲区大小实际上是在控制 channel 在没有接收者读取数据时能够存储多少个元素。
//当缓冲区大小为3时,这个channel可以存储3个元素,当缓冲区已经有3个元素时,如果还有新的数据需要写入channel,
//那么会阻塞直到缓冲区有空闲位置,所以这里的缓冲区并不会影响结果的输出。
go func() {
defer close(src) //在当前函数func()执行完毕之后关闭 src channel。关闭后,就不能再向它写入数据,但是可以从它读取数据。
for i := 0; i < 10; i++ {
src <- i //意思是将 i 写入 src channel。这里的 i 是一个循环变量,在循环体中每次迭代时都会被赋值。所以这个循环体中的语句就是不断地将 0~9 的数字写入 src channel。
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}() //该处缓冲为3是为了不让下方的消费者的消费速度影响生产速度
for i := range dest {
//复杂操作
fmt.Println(i)
}
}
func main() {
CalSquare()
}
1.3 sync
sync包下的关键字,有lock、waitgroup,主要实现并发安全操作和协程的同步
1.3.1 锁-lock
// 通过共享内存实现通信(不推荐)-并发安全性的考虑
package main
import (
"fmt"
"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) //暂停一秒钟
fmt.Println("WithoutLock:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second) //暂停一秒钟
fmt.Println("WithLock:", x)
}
func main() {
Add()
}
1.3.2 waitgroup
// waitgroup也在sync下,有Add\Wait\Done.维护一个计时器,可以增加和减少
package main
import (
"fmt"
"sync"
)
func hello(i int) {
println("hello goroutine :" + fmt.Sprint(i))
}
func main() {
//使得27goroutine里的Sleep变得优雅
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()
}
2 依赖管理
2.1 演变
GOPATH->升级为Go vendor->升级为Go Module
2.2 三要素
- go.mod配置文件,描述依赖(依赖管理基本单元-原生库-单元依赖)
module xxx(从哪里找到这个模块 如github仓库。每个package下都要有。)
go 1.16 (原生库)
require(
module_path 版本号(有语义化版本和基于commit伪版本两种)
...(可以有直接依赖、间接依赖、incompatible不兼容代码)
)
- Proxy为中心仓库管理依赖库
Proxy是一个服务站点。为了解决第三方代码托管平台对于版本更新的一系列问题,Proxy会缓存软件内容,保证依赖的稳定性和可靠性。以后就可以从Proxy拉取依赖。
GOPROXY="https://proxy1.cn,https://proxy2.cn,direct"则会按照proxy1->proxy2->direct(第三方源站)的顺序去寻找依赖 - go get/mod为本地工具
go get example.org/pkg
@update(默认)
@none(删除依赖)
@v1.1.2(tag语义版本)
@23dfdd5(特定的commit)
@master(分支的最新commit)
go mod
init(初始化,创建go.mod文件)
download(下载模块到本地缓存)
tidy(增加需要的依赖,删除不需要的依赖)