这是我参与「第五届青训营」伴学笔记创作活动的第三天。
这一篇是之前学习过的课程笔记总结。记录自己学习过程。
知识要点
- 语言进阶
- 依赖管理
- 测试(中篇和下篇继续)
- 项目实战
知识点详解
语言进阶
这里主要讲解并发编程:
- 并发 vs 并行
- 协程(支持并行的关键, Goroutine)
协程:用户态,轻量级线程,栈KB级别。->Go语言用户操作
线程:内核态,县城跑多个协程,栈MB级别。->昂贵的系统资源,线程可以并发运行多个协程。 - 如何开启一个协程
案例:快速打印 hello goroutine (0-4) 不要求打印顺序
代码如下,注意以下几点:
1)加上go关键字(为代码创建协程)
2)使用sleep进行阻塞(保证子协程执行完之前线程不退出)其中一次执行结果如下:package main import ( "fmt" "time" ) func hello(i int) { println("hello goroutine:" + fmt.Sprint(i)) } func HelloGoRoutine() { for i := 0; i < 5; i++ { go func(j int) { hello(j) }(i) } time.Sleep(time.Second) } func main() { HelloGoRoutine() }
- 协程交互
go语言提倡通信共享内存,而不是共享内存进行通信
前者主要使用队列,后者需要加锁实现- 具体实现->Channel
make(chan 元素类型,[缓存大小])
分为两种:
无缓存通道 如make(chan,int)
有缓存通道 如make(chan,int,2)
两者区别如下图所示:
一个具体使用案例:
要求如下- A子协程:发送数字0-9,
- B子协程计算数字平方,
- 最后主协程输出最后平方数
注意:
1)考虑消费者B消费速度可能比较慢,生产者A逻辑简单。添加带缓冲的channel减少两者速度上的不匹配
2)defer 延迟资源关闭package main func CalSquare() { src := make(chan int) dest := make(chan int, 3) // A go func() { defer close(src) for i := 0; i < 10; i++ { src <- i } }() // B go func() { defer close(dest) for i := range src { dest <- i * i } }() for i := range dest { println(i) } } func main() { CalSquare() } - 具体实现->Channel
- 并发安全 Lock
共享内存支持通信->多个协程同时对一块内存进行操作。
案例目标
对变量执行2000次+1操作,5个协程并发
主要区别加锁 (使用sync.Mutex实现) 和不加锁的区别:其中一次执行结果如下:package main import ( "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) println("Without lock:", x) x = 0 for i := 0; i < 5; i++ { go addWithLock() } time.Sleep(time.Second) println("With lock:", x) } func main() { Add() }
- WaitGroup
更好的执行阻塞的方法,而不是使用time.Sleep
更改快速打印hello goroutine如下:package main import ( "fmt" "sync" ) func hello(i int) { println("hello goroutine:" + fmt.Sprint(i)) } func HelloGoRoutine() { var wg sync.WaitGroup // 定义一个计数器 for i := 0; i < 5; i++ { go func(j int) { defer wg.Done() hello(j) }(i) } wg.Wait() } func main() { HelloGoRoutine() }
依赖管理
- 背景:需要使用他人依赖包可以加快开发效率
- Go 依赖管理演进
目的:不同环境(项目)依赖版本不同,控制依赖库的版本。 - GoPath
问题如下: 不能实现package的多版本控制 - 改进 Govender
问题如下
1)无法控制依赖的版本
2)更新项目有可能出现依赖冲突,导致编译出错 - Go Module
方式:
通过go.mod 文件管理依赖包版本 通过go get/go mod 指令工具管理依赖包
**终极目标:**定义版本规则和管理项目依赖关系 - 依赖管理三要素
- 配置文件,表述依赖 -> go.mod
- 中心残酷管理依赖库 -> Proxy
- 本地工具 go get/mod
- 实操案例
注释1:
major->大功能更新,不同major可以看作代码隔离
minor->增加新增功能,要求同一个major之间兼容
PATH->主要修复bug等功能
注释2:
1.版本前缀(和语义化版本一致)
2.时间戳
3.提交commit哈希码 - 一些关键字
indirect(没有直接依赖)
incompatible(对于没有go.mod文件但是主版本为2+依赖,会加上这个关键字) - 依赖图
答案为B(选择最低兼容版本)(后续在Go 语言工程进阶(中)继续补充)