依赖配置 - version开始,就开始很难听懂了,需要结合很多课后配套资料查阅很多文档和网站....然而好像没有那么多时间,一天给3小时学Go真的顶天了.....还有算法和Linux的Mysql...
这几天学Go已经把算法给挤掉了.....下步要权衡一下,好好分配下时间
目录
🐘并发编程
🍈并发与并行
🍈Goroutine -- 协程与线程
📚Git从Github拉取代码
🍉CSP
🦖Channel
🦖并发安全Lock
🦖WaitGroup
🐅依赖管理
🌼简介 + 3个阶段
🌼依赖配置
🌼依赖分发
🌼工具篇:go get/mod
🌳依赖管理三要素
🐘并发编程
主要涉及,Go并发编程的相关概念
🍈并发与并行
我们两个人在吃午饭。你在吃饭的整个过程中,吃了米饭、吃了蔬菜、吃了牛肉。吃米饭、吃蔬菜、吃牛肉这三件事其实就是并发执行的。对于你来说,整个过程中看似是同时完成的的。但其实你是在吃不同的东西之间来回切换的
还是我们两个人吃午饭。在吃饭过程中,你吃了米饭、蔬菜、牛肉。我也吃了米饭、蔬菜和牛肉。我们两个人之间的吃饭就是并行的。两个人之间可以在同一时间点一起吃牛肉,或者一个吃牛肉,一个吃蔬菜。之间是互不影响的
上图并发,下图并行
并发的多个任务之间是互相抢占资源的;并行的多个任务之间是不互相抢占资源的
面试必考的:并发和并行有什么区别?-腾讯云开发者社区-腾讯云 (tencent.com)
实际开发中,并行可以理解为实现并发的一种手段
而Go语言👇,是为高并发而生的,它可以发挥多核优势,高效运行
🍈Goroutine -- 协程与线程
(1)进程是操作系统进行资源分配的基本单位
(2)线程又叫做轻量级进程,是进程的一个实体,是处理器任务调度和执行的基本单位位。它是比进程更小的能独立运行的基本单位
(3)协程,又称微线程,是一种用户态的轻量级线程
对于操作系统来说,一个任务就是一个进程(Process)。比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程
And...
有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,进程内的这些“子任务”称为线程
And...
由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样
协程最大的优势就是协程极高的执行效率
1,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销
2,和线程切换相比,线程数量越多,协程的性能优势就越明显
3,不需要多线程的锁机制,因为只有一个线程
4,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好
Final...
所以协程的执行效率比多线程高很多。
此外,一个线程的内存在MB级别,而协程只需要KB级别
一文快速了解进程、线程与协程-腾讯云开发者社区-腾讯云 (tencent.com)
内核态与用户态
内核态运行操作系统程序,操作硬件;用户态运行用户程序。
(1)程序运行在3级特权级上时,可以称之为运行在用户态
(2)运行在0级特权级上时,称之为运行在内核态
而Go语言,一次可以创建上万个协程,这也是Go为什么更适合高并发场景的根源
代码(Go创建多个协程)
package main
import (
"fmt"
_ "fmt"
"time"
_ "time"
)
func hello(i int) {
//Sprint传入参数i的字符串形式,便于字符串相加
println("hello goroutine : " + fmt.Sprint(i))
}
// go创建协程, 即启用goroutine, 只需要在函数前增加 go 关键字
func HelloGoRoutine() {
for i := 0; i < 5; i++ { //创建5个goroutine
go func(j int) { //匿名函数接受的参数
hello(j)
}(i)
}
//保证子协程执行完之前, 主协程不退出
time.Sleep(time.Second) //使goroutine停顿1秒
}
func main() {
HelloGoRoutine()
}
//乱序输出,因为是通过并行打印的
📚Git从Github拉取代码
由于字节内部课,每个课都需要从Github拉取课程源码,怕忘了,在这里总结下
使用git从github上拉取项目到本地_51CTO博客_idea从git上拉取项目
注意!
(1)是在对应目录空白处,右键,点击“Open Git Bash Here”
(2)然后,将Github复制的代码,先git clone,再右键粘贴Paste
(3)拉取前要关闭代理👇
🍉CSP
CSP 即 Communicating Sequential Processes, “通信顺序进程”, CSP是Go语言特有的并发模型
Go的CSP并发模型,是通过goroutine和channel实现的
Go的CSP并发模型 - 简书 (jianshu.com)
Go语言所提倡的是,“不要以共享内存的方式来通信,相反,要通过通信来共享内存。”
普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问
浅谈Go并发之CSP并发模型、协程并发-云社区-华为云 (huaweicloud.com)
🦖Channel
Channel · Go语言中文文档 (topgoer.com)
(1)channel是一种类型,一种引用类型,你可以把它看成一个管道
(2)一种go协程用以接收或发送消息的安全的消息队列,
channel
就像两个go协程之间的导管,来实现各种资源的同步
第二个参数是capacity(容量)
无缓冲通道
有缓冲通道
package main
func CalSquare() {
src := make(chan int) //无缓冲的通道
dest := make(chan int, 3) //有缓冲的通道
go func() {
defer close(src) //人物完成时关闭通道
for i := 0; i <= 10; i++ {
src <- i //子协程A将0-10数字发送到src通道中
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i //子协程B接收数字计算平方,并发送到dest通道
}
}()
for i := range dest {
//复杂操作...
println(i) //主协程中依次接受通道dest中数据,并打印
}
}
func main() {
CalSquare()
}
可以看到输出是有序的,因为
虽然启动了多个 goroutine 并行执行,但由于通道的同步机制,保证了数据的有序传递
🦖并发安全Lock
不加锁会输出预期外的结果(undefined),这就是并发安全问题
所以开发中,要尽量避免,对共享内存,进行非并发安全的读写操作
换言之,我们要对临界区进行权限控制
注意,sync. 需要导入包,而且需要Goland 1.3版本以上
package main
import ( //引入包
"sync"
"time"
)
var (
x int64 //声明x变量作为共享资源,用于计数
lock sync.Mutex //声明lock变量作为互斥锁,保护对x的并发访问
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock() //获取互斥锁
x += 1 //计算
lock.Unlock() //释放互斥锁
}
}
func addWithoutLock() { //没有互斥锁,直接对x进行并发访问
for i := 0; i < 2000; i++ {
x += 1
}
}
func Add() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock() //创建5个goroutine并发执行函数
}
time.Sleep(time.Second)
println("WithoutLock:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock() //
}
time.Sleep(time.Second)
println("Withlock:", x)
}
func main() {
Add()
}
WithoutLock: 9784
Withlock: 10000
🦖WaitGroup
深入理解 go sync.Waitgroup - 掘金 (juejin.cn)
WaitGroup是Go语言里,sync包里的结构体,用来阻塞主协程,等待所有协程执行完,是较为常见的并发控制方式
计数器:开启协程, +1;执行结束, -1
主协程阻塞知道计数器为0
package main
import ( //引入包
"fmt"
"sync"
)
func hello(i int) { //定义内部函数
println("hello goroutine : " + fmt.Sprint(i))
}
func ManyGoWait() { //并发执行多个goroutine
var wg sync.WaitGroup //创建sync.WaitGroup类型变量
wg.Add(5) //开启5个协程
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done() //子协程,任务执行结束
hello(j)
}(i) //匿名函数, 传入索引值i
}
wg.Wait() //阻塞主程序,等待所有goroutine完成
}
func main() {
ManyGoWait()
}
🌼总结
(1)协程:Go可以通过高效调用模型,来实现协程(goroutine)
(2)通道:Go可以通过通信来共享内存,利用的就是通道(channel)
(3)Sync:包含Lock,WaitGroup等关键字,目的是实现并发安全操作,以及协程间的同步
🐅依赖管理
🌼简介 + 3个阶段
我们应该将更多精力放在应用逻辑的实现上
什么是依赖管理?
(1)依赖管理,是指在什么地方以什么形式引入外部代码
(2)没有项目任务是孤立存在的。除了第一个项目任务之外,每个任务或活动都以某种方式依赖于其他活动
项目管理软件中什么是依赖管理,具体有什么作用? (xjx100.cn)
Go的依赖管理经历了三个阶段👇
Go 依赖管理详解 - 掘金 (juejin.cn)
应用最广泛的是Go Module,依赖管理的目的是:
(1)管理不同项目依赖的版本
(2)控制依赖库的版本
环境变量Gopath
(1)项目代码直接依赖 src 下的代码
(2)go get 下载最新版本的包到 src 目录下
Go Vendor
(1)项目目录下增加vendor文件,所有依赖包,以副本形式放在 .../vendor 下
(2)依赖寻址方式:verder -> GOPATH
(3)每个项目引入一个依赖副本,解决多个项目需同一个package,的依赖冲突问题
Verdor 弊端
Vendor也只是依赖项目源码,无法具体地标识依赖了哪个版本,因此更新项目时,还是会出现依赖冲突,导致编译错误
Go module👇
(1)通过 go.mod 文件,管理依赖包版本
(2)通过 go get / go mod 指令工具,管理依赖包
最终目标:定义版本规则和管理项目依赖关系
依赖管理三要素
1,go.mod
配置文件,描述以来
2,Proxy
中心仓库管理依赖库
3,go get / go mod
本地工具
🌼依赖配置
分5个部分结合代码介绍:
go.mod -> version -> indirect -> incompatible -> 依赖图
go.mod
go.mod文件
module example.com/project/app // 替换为你自己的域名或者其他唯一标识
go 1.16
require (
github.com/example/lib1 v1.0.2 // 替换为实际的库路径和版本号
example.com/example/lib2 v1.0.0 //indirect,替换为实际的库路径和版本号
github.com/example/lib3 v0.1.0-20190725025543-5a5fe074e612 // 替换为实际的库路径和版本号
example.com/example/lib4 v0.0.0-20180306012644-bacd9c7ef1dd //indirect,替换为实际的库路径和版本号
github.com/example/lib5/v3 v3.0.2 // 替换为实际的库路径和版本号
github.com/example/lib6 v3.2.0+incompatible // 替换为实际的库路径和版本号
)
version
开始一点都听不懂了,应该是有很多前置知识的,,,算了,先听一遍,适当做做笔记先
...
语义化版本 和 基于commit的伪版本
indirect
indirect关键字,A对B直接依赖,A对C间接依赖
incompatible
(1)对于没有go.mod文件,并且主版本2+的依赖,会+incompatible
(2)主版本2+模块会在模块路径增加 /vN 后缀
依赖图
问:X项目依赖A,B两个项目,A,B分别依赖C项目的v1.3和v1.4版本,最终编译时,所使用的C项目版本为下列的哪项(单选):
A. v1.3
B. v1.4 √
C. A用到C时v1.3,B用到C时v1.4
要选择,最低的兼容版本v1.4,且v1.3和v1.4之间肯定互相兼容的
🌼依赖分发
回源
(1)通过Github这样的第三方代码托管平台,go.mod中定义的依赖,可以直接在对应的仓库下载,来完成依赖分发。
(2)但是,直接用版本管理仓库下载依赖,存在一些问题👇
1,当软件作者直接在代码平台修改软件版本时,如果我们下次需要构建项目,会发现,之前所以来的版本丢失
2,当软件仓库被删除,依赖可用性就破坏了
3,大流量场景,增加负载
Proxy
(1)为了解决上述问题,Proxy出现了
(2)它会缓存站中的软件内容,缓存的软件版本不会改变,在源站软件删除之后依然可用
(3)使用Go Proxy后,构建时会直接从Go Proxy站点拉取依赖
变量GOPROXY
🌼工具篇:go get/mod
go get example.org/pkg + 👇
example.org是一个示例域名
go get example.org/pkg,比如go get github.com/username/repo
从github下载和安装一个包
go mod + 👇
🌳依赖管理三要素
(1)go.mod -> 配置文件,描述依赖
(2)Proxy -> 中心仓库管理依赖库
(3)go get / go mod -> 本地工具
总结
可算对Go Module有了个概念,但也只是概念,需要结合后续实际开发进行巩固