这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
并发编程
并发和并行
- 并发:早期计算机的 CPU 都是单核的,一个 CPU 在同一时间只能执行一个进程/线程,当系统中有多个进程/线程等待执行时,CPU 只能执行完一个再执行下一个。简单来讲,并发就是多线程程序在一个核的CPU上的运行。
- 并行:并行是针对多核CPU提出的,多核CPU真正实现了“同时执行多个任务”。并行即多线程程序在多个核的CPU上的运行。
Go就可以发挥多核优势,最大限度调用计算机资源
协程-Goroutine
- 线程:内核态,线程跑多个协程,栈MB级别
- 协程:用户态,轻量级线程,栈KB级别
快速打印hello goroutine:0~hello goroutine:4,就要快速开启多个协程
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
如上代码,Go语言要开启协程,在调用函数时,在函数前加上go关键字
CPS模型
如上,Go语言提倡通过通信共享内存,于是,GO语言采用了CPS模式
让我们先了解一下Channel通道
Channel操作:make(chan 元素类型,[缓冲大小])
根据缓冲通道的大小,通道又可以分为:
- 无缓冲通道:发送与接收同步,也被称为同步通道。
- 例如:
make(chan int) - 有缓冲通道:通道会阻塞发送。
- 例如:
make(chan int,2)
- 缓冲通道就像是货架,是为了照顾消费者速度
- 使用带缓冲的队列,不影响生产者执行效率
举一个例子:
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
//复杂操作
println(i)
}
}
src和dest两个channel输出0-9平方,保证了输出的顺序性,即并发安全。
并发安全Lock
- 计算权限,控制释放
- 保证内存空间的安全
让我们来看一个例子:
对变量执行2000+1次操作,5个协程并发执行
我们可以清楚的看到,加锁后保证并发安全,得到准确的计算结果。
WaitGroup
- 实现并发任务不重复 协程的阻塞是延时进行的,我们并不知道其具体的时间,于是需要引入WaitGroup解决这个问题。
以下有一个例子:
如图可见,WaitGroup内部属于维护一个计数器,计数器默认值为0,计算并发任务。
计数器如下:
- 开启协程+1
- 执行结束-1
- 主协程阻塞直到计数器为0,表示完成任务
依赖管理
Go语言不同环境(项目)依赖的版本不同,需要能控制依赖库的版本也不同,依赖管理显得就十分重要了。
依赖管理分为以下三个阶段:
- gopath
- Go Vedor
- Go Module
gopath
go语言的环境变量(工作区),里面有三个关键点:
- bin:项目编译的二进制文件
- pkg:项目编译的中间产物
- src:项目源码,go get里下载的最新版本包,工程都在这里
gopath也有弊端:比如A和B,依赖于两个不同的package,便无法同时构建成功(无法实现package的多版本控制)
Go Vedor
为了解决gopath的问题,于是引入Go Vedor。
- vender项目下存放副本
- 解决多个项目需要同一个package依赖的冲突问题
弊端:
- 无法控制依赖的版本
- 更新项目又可能出现依赖冲突,导致编译出错
Go Module
为了解决go vendor的弊端,引入Go Moudle
- 通过go.mod文件管理依赖包版本
- 通过go get/go mod两个指令工具管理依赖包
管理依赖:
管理依赖三要素:
- 配置文件,依赖描述(go.mod)
- 中心仓库管理依赖库(Proxy)
- 本地工具(go get/mod)
依赖配置
组成:依赖管理基本单元,原生库,单元依赖,项目涉及很多包。
要单独引用
- 在每个包下就都要建立go.mod文件
- 原生库版本号一定要写
- 描述单元依赖:模块modlepath+版本号,定义某一次提交
version
语义化版本:MAJOR,MINOR(保证新功能,前后兼容),PATCH(代码修复)
基于commit伪版本:
vX.0.0-yyyymmddhhmmss-abcdefgh1234
版本格式,时间戳,哈希码(前缀)
indirect(关键字)
go mod没有直接导入的依赖模块,就会被indirect标识出来
incompatible
标识出可能不兼容的代码
依赖图
go进行版本选择时,首选最低兼容版本,比如1.3和1.4兼容,选择1.4
依赖分发
回源时,若直接从代码库源站引用,有以下问题:
- 无法保证构建稳定性(增加,修改,删除软件版本)
- 无法保证依赖可用性(删除软件)
- 增加第三方压力(代码托管平台负载问题)
Proxy: 保证依赖路径,缓存源站内容,实现稳定可靠的依赖分发
变量GOPROXY:服务站点URL列表,direct(返回源站),如下:
GOPROXY="https://proxy1.cn,http://proxy2.cn,direct"
工具
go get
- update:默认
- none:删除依赖
- v1.1.2:拉取特定语义版本
- 23dfdd5:拉取特定commit
- master:拉取分支,拉取到最新commit
go mod
- init:初始化项目,创建go.mod文件
- download:下载模块到本地缓存
- tidy:增加需要的依赖,删除不需要的依赖
这里有个小建议,写新项目时,可以先用tidy删除依赖,减少项目时间
参考
- 字节跳动青训营Go语言进阶—工程进阶课程
- c语言中文网