[ Go语言基础 | 青训营笔记]

47 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

本文第一次编辑于1.16晚,第二次编辑于1.17,其实作为一个初学Go的人,觉得Go有很多地方和Java是类似的,所以为了好理解,接下来文章中难免会出现Java中常见的名词。

1. 本节课重点内容

Go并发编程、Go的依赖管理、Go代码的测试 (这里看到后面实在是集中不了,先挖个坑明天在补)

2. 详细知识点介绍

2.1 Go并发编程

协程:

课程中提出了一个协程的概念,虽说根据课上的知识,Go中线程和协程不是一个概念,协程是用户态的,而线程是内核态的。但是个人认为,Go中的协程和Java中的线程很类似,它们在做一个类似的工作:将任务交给第二个线程去执行。

协程使用go关键字来进行创建,下面是一个例子

image.png

在每次进入for循环中开启了一个协程,用来打印数字i (另外我有一个问题,他这个协程的底层是不是也和Java类似,cpu调度到哪个协程就运行哪个协程)

Channel

是用来进行协程间的通信,可以分别通过make(chan int)、make(chan int,2)来创建一个无缓冲、有缓冲通道。课上的图也很形象,我这里也把它贴上来

image.png

这里其实我有几个问题:

Q1:无缓冲通道的就类似于同步队列?(当然这里说是队列好像不太恰当)

Q2:有缓冲通道的就相当于异步队列?

Q3:有没有这么一种场景,在这张图中

1673883786021.png

如果我goroutine2运行到一半的时候时间片到了,但是这个时候缓冲通道里的数据还没有读完,那下一次goroutine2再次运行的时候是怎么知道缓冲通道里的数据哪些是读过的哪些是没有读过的。是缓冲通道里有一个类似于指针的东西吗?

锁和线程同步

Go中的锁把它类比Java中的锁也是没啥问题的。课程有个小例子

41e2149bae9d82a5bf572743f97de97.png

其实就是在加锁和不加锁的情况下分别让x自增2000次,有过编程基础的朋友们都应该知道在无锁情况下,最后输出的答案是不确定的,但是在加锁的情况下答案就是正确的。 (但是我这里有个问题,Go中的x+=1操作也是和Java中类似,是一个 取值、+1、赋值的过程吗?)

Go中的线程同步可以使用WaitGroup来实现,和Java中的CountDownLatch十分相似,都是当前协程在变量变为0之前不允许向下执行。以下是我对课上例子的执行流程的理解,也希望有朋友能告诉我WaitGroup的底层实现。

106e5d37eba121c13eece84f6806d70.png

首先在主协程(这个词是我瞎叫的)里定义一个WaitGroup类型的变量wg并赋值为5,并且每次进入for循环的时候就创建一个协程,然后将wg的值-1,当wg的值不为0的时候,主协程阻塞,当wg的值变为0的时候,主协程才继续向下运行。

2.2 Go中的版本依赖管理

根据课上老师所讲,Go的版本依赖管理经历了几个阶段的迭代,我们这里直接说最后一个阶段也就是我们当前所使用的版本依赖管理工具。相信有过Java开发的朋友对maven并不陌生,maven就是实现依赖管理的功能。Go中通过go.mod来对依赖版本进行管理,我个人把它类比为Spring中的pom.xml文件。

课上老师有讲到Go拉取依赖的过程,我觉得这个也是一个重要的知识点。整个过程如下图所示:

6d10b3a9aa412a457beacbffcc3de16.png

图上用红色框起来的称他们为源站。相信朋友们在安装Go语言的时候都配置过GOPROXY这个变量,这个变量的值就是一个代理地址,Go可以从这个代理地址中去拉取依赖,如果这个代理地址中没有我们要的依赖,那么Go再去源站进行拉取。 (夜深了,挖个坑,剩下的知识点留着明天再写把)

2.3 Go课堂中的第一个完整项目

项目使用gin框架来实现web开发,因为项目中对外暴露的接口只有一个,所以我们就顺着这个接口的实现逻辑来讲解一下这个项目。

f155524f2a0cc732945917378ceccbb.png

可以看到这个路由先从请求路径中取出topicId的值,然后将这个值交给controller中的QueryPageInfo方法。

e03ab970136f3659872a701b243e95a.png

QueryPageInfo方法中,在16行先将传入的topicId转换为10进制、精度为64位的int类型数据,如果转换成功的话就执行23行的QueryPageInfo方法。

4bf1064530a8bc112ef05c7b5210d5c.png

可以看到,在QueryPageInfo方法中,先执行了红色框中的NewQueryPageInfoFlow方法,这个方法的主要目的其实就是包装传入的topicId数据,在包装完成之后就执行Do方法。

d4ca9f9ed3eb4e35d232d79984f4dd5.png

Do方法其实就是对返回的数据进行封装,我们对其中的checkParam、prepareInfo、packPageInfo进行介绍。

a205f65c7e634d17b376ad99aab81fe.png

首先是checkParam方法。可以看到,checkParam方法其实就是对传入的topicId进行判断。

a8d932178c0feee11747440964c748e.png 首先在54行的代码中,创建了一个sync.WaitGroup类型的变量并赋值为2,开启两个协程分别获取topic和post,我们来对58行红色框中的代码进行探讨。

808ab326f740b8f069e8c4ca13fa448.png

可以看到,在18行的代码中声明了一个sync.Once类型的变量,这个变量的作用就是希望23-25行的代码只执行一次。(这里个人感觉有点类似于Spring中的单例模式)具体sync.Once的介绍可以看这篇博客 解析 Golang sync.Once 用法和原理。我们用一句话总结NewTopicDaoInstance函数的整个作用,就是令topicDao全局唯一。

执行完NewTopicDaoInstance方法之后,我们继续回到上上图中的58行代码,去看一下QueryTopicById方法的作用是什么

c363f521111b954523807a476d5fd22.png

由于这里没有连接数据库,所有的数据都使用Map集合来进行储存,所以QueryTopicById执行的就是根据id在map集合中查找对应Topic的操作。

在上上上图中,62行又开启了一个新的协程,我们直接来看QueryPostsByParentId方法的函数体。

b99dd91b7fa897b063988997837f408.png 可以看出这里也是在Map集合中查数据。

b99dd91b7fa897b063988997837f408.png

packPageInfo方法也是在Map中找数据。

项目的大致流程我们都介绍完了,都是一些很基本的逻辑,就是查询数据封装并返回给前端,这里我觉得比较值得注意的就是包装的思想和sync.Once关键字的使用。

项目总结:

其实这个项目的结构和逻辑并不算多复杂,不过还是有一些很有趣的点。比如接触到了sync.Once关键字,这个关键字的作用也让我有点找到了Spring式编程的感觉。(第二次编辑于1.17,还是有一个小的知识点没写到,留着以后添加吧)