这是我参与「第五届青训营 」笔记创作活动的第3天
笔者才学疏浅,如有问题请指出
前言
每个编程语言除了有基础语法还会有一些基础语法之上的进阶内容。本篇文章主要讨论和记录笔者的go语言工程进阶部分内容的学习,分为并发编程、依赖管理两个板块来记学习和记录。
并发编程
在学习go的并发编程之前,我们先了解一下什么是进程和线程以及并发和并行。
1.进程和线程
进程
说通俗易懂点,打开电脑的任务管理器就能看到很多进程,也就是说每一个在运行的程序(软件、驱动等)都是一个进程。在常见的操作系统中,进程既是一个操作系统的基本分配单元,也是操作系统的基本执行单元。进程有三个状态就绪态、阻塞态、运行态。
- 就绪态:即程序准备就绪达到了可以运行的状态,只等待系统分配资源。
- 阻塞态:不满足程序运行条件等待条件满足时的状态。
- 运行态:程序占用了系统资源正在运行的状态。
线程
线程是进程中执行运算的最小单元,是操作系统执行处理机制的基本单位。每个进程至少有一个线程,线程可以利用进程所拥有的资源执行调度和运算。线程只能有一个进程,但进程可以有多个线程。
2.并发和并行
并发
当cup资源只有一个的时候,同时有多个进程或线程满足条件需要使用资源,进程或线程之间就要进行资源的争夺,争夺到资源的进程或线程执行任务,其他的则等待不执行。也就是在一个时间段内,三个满足条件的进程或线程只有一个在执行,只是cpu处理速度太快让我们感觉三个进程或线程是同时执行的。
并行
我们都知道一条宽大的马路有很多条车道,车辆可以在马路上同时并着行驶。多核的cpu就类似这样的情况,每一个核执行一个任务,多核也就可以多个任务同时执行。并行就不用争夺资源,并行状态下的线程分布在不同的核上。
3.go的并发编程
Goroutine(协程)
在一个go程序里面,main函数对应了主线程,而在主线程里面开一个轻量级的线程就被叫做协程。
- 线程:用户态,轻量级线程,栈MB级别。
- 协程:内核态,线程跑多个协程,栈KB级别。
使用go关键字即可快速开启一个协程,下面为教程中的例子:
Channel(通道)
协程之间用来通信和传输数据的工具叫通道,分为有缓存通道和无缓冲通道。顾名思义,缓冲通道可以提供一片空间暂时存放数据,不过当缓冲满后需要释放缓冲才可继续传输数据。通道的简单定义如下:
//make(chan 元素类型,缓冲大小)
make(chan int,2)//有缓冲通道
make(chan int)//无缓冲通道
举一个教程中的例子:
Lock(锁)
并发编程必然涉及到一个并发安全的问题,多个不同的线程可能会对同一块内存空间进行操作造成内存泄漏等很多安全问题,这时候就需要一个锁来保证内存空间的安全。
var lock sync.Mutex//定义一个锁
lock.Lock()//上锁
lock.UnLock()//解锁
我们来看一个教程中的例子:
我们看到了加锁和不加锁得到的结果是不一样的,对x那个变量加锁后保证了安全,使得计算结果准确。
WaitGroup
在Goroutine那里的样例里面协程的阻塞是使用延时来进行的,但在实际情况中我们并不能具体的知道那个准确合理的时间,这时候就要引入WaitGroup来解决那个问题了。
//定义一个WaitGroup
var wg sync.WaitGroup
wg.Add(5)//计数器+5
wg.Done()//计数器-1
wg.Wait()//阻塞直到计数器为0
来看一下教程中WaitGroup的使用:
依赖管理
很多时候,我们程序的开发和运行都是需要依赖包才得以实现,依赖管理就显得非常重要了。go的依赖管理经历了gopath、govendor、gomodule三个阶段,我们简单谈谈gopath和现在使用的gomodule。
gopath
GOPATH是我们go的环境变量之一,为go工作目录的路径,高版本会自动配置GOPATH。go的工作目录下有三个子目录分别是bin、pkg、src。
- bin:存放项目编译的二进制文件。
- pkg:存放项目编译的中间产物。
- src:存放项目源码。
项目代码直接依赖src下的代码,go get命令可下载最新的依赖包到src下,不过gopath的弊端是无法进行依赖包的多版本控制。
gomodule
在前两个阶段都存在极大弊端的情况下,gomodule阶段诞生了。
- 通过go.mod文件管理依赖包版本。
- 通过go get/go mod 指令管理依赖包。
- 终极目标:定义版本规则和管理项目依赖。
- 依赖管理三要素:go.mod、Proxy、go get/mod
go.mod
配置文件,描述依赖。
在1.6版本之后,创建项目会自动生成一个go.mod文件(类似java项目的pom.xml文件),还会引入一个go.sum文件(记录依赖的hash值,防止依赖被人修改,详情可参考(5分钟玩转go.sum - 掘金 (juejin.cn))。
在依赖冲突的情况下,go会默认选择最低的兼容版本,举个例子:A依赖C-1.2,B依赖C-1.3,C-1.4和C-1.5兼容了C-1.2和C-1.3的功能,那么go会默认选择C-1.4的版本。
Proxy(依赖分发)
GOPROXY=https://goproxy.cn,direct
上面的即go拉取依赖的服务站点地址配置,按从左往右的优先级去查找,直到找不到会查找下一个,最后是direct(默认的源站)。一般来说建议配置一个国内镜像这样在下载依赖的时候速度会快很多。当依赖被拉取下来后会在本地进行管理。注意:如果项目有导包,一定要将依赖拉取到本地保证本地有这个依赖,否则直接在导包的位置导入一个包的URL地址也是会爆红的。
- Proxy保证了依赖的稳定性:本地依赖库,无需担心依赖作者、修改依赖版本导致项目不可用。
- Proxy保证了依赖可用性:即使依赖作者删除依赖,本地依赖依然可以保证项目的开发和运行。
小结
学习了go的并发编程、依赖管理,不管是go还是java,并发的处理和项目的依赖管理都是非常重要的。因为go的一些特性天然支持高并发,对于并发的处理没有java那么麻烦;java使用maven来管理项目,go用go.mod、Proxy、go get共同来管理依赖,两者有相似之处,各有其优缺点。
参考
- 字节跳动青训营Go语言进阶——工程进阶课程
- 进程、线程、多线程、并发、并行 详解 - 腾讯云开发者社区-腾讯云 (tencent.com))