这是我参与「第三届青训营 -后端场」笔记创作活动的的第一篇笔记
一. go的依赖管理
- 概述:利用已经有的封装好的包来提高开发的效率
- 依赖管理的演进
- gopath
- go vendor
- go module
- 注意:版本的选择,不同环境的依赖版本不同
- 依赖配置图
- 一个项目即依赖A又依赖B,而A和B又分别依赖了C项目的v1.3和v1.4,最终编译所使用的C项目的版本v1.4,原因是选择最近的兼容版本。
- 依赖分发
- 为了获得一个稳定、可靠的开发环境,选择Proxy,他可以缓存原来网站中的内容,保持版本不变。如图所示
5. 工具
- go get example.org/pkg
- 包括:1.默认@update、2.删除依赖@none、3.tag版本语义版本@v1.1.2、4.特定的commit@23dfdd5、5.分支的最新commit@master
- go mod -包括:1、初始化,创建go.mod文件 init、2.下载模块到本地缓存 download、3. 增加需要的依赖,删除不需要的依赖 tidy 二. 测试
- 概述:在开发后进行软件测试可以避免事故的发生
- 分类:
- 回归测试
- 集成测试
- 单元测试
- 规则
- 所有测试文件要以_test.go结尾
- func TestXxx(*testing.T)
- 初试化逻辑放到TestMain中
- 单元测试-mock
- 快速mock函数
- 为一个函数打桩
- 为一个方法打桩
- 基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力 三. 项目实战
- 需求分析
- 实习社区话题页面
- 展示话题(标题和文字描述)和回帖列表
- 暂时不考虑前端页面实现,仅仅实现本地web服务
- 话题和回帖数据用文件存储
- 需求用例
- E-R图
- 软件开发分层结构
- 数据层:数据model,外部数据的增删改查
- 逻辑层(服务层):业务entity,处理核心业务逻辑输出
- 视图层(控制层):视图view,处理和外部的交互逻辑
- 组件工具
- gin高性能go web框架
- go mod
- go mod init
- go get gopkg.in/gin-gonic/gin.v1@v1.3.0
- repository
- repository-index

四.goroutine基本模型和调度设计策略
- 单一执行流程、计算机只能一个任务一个任务处理
- 进程阻塞所带来的CPU浪费时间
- 多线程擦操作系统 - 采用轮寻调度,即用调度器对进程A、进程B、进程C进行轮寻调度
- 在做进程之间的切换,线程之间会有切换成本,这些切换成本被称为CPU浪费时间成本,所以进程/线程的数据量越多,切换成本就越大,也就越浪费
- 进程/线程消耗内存
- 线程的切分
- go最适合的用多对多的关系
- goland对协程的处理
- golang对灵活调度的处理
- 旧的调度器,缺点:激烈的锁竞争、延迟和额外的系统负载、增加了系统开销
- 旧的调度器,缺点:激烈的锁竞争、延迟和额外的系统负载、增加了系统开销
- goland对调度器的改进
- 总体思想
- work stealing偷取机制
- hand off机制
- 利用并行
- 抢占
- 全局G队列
- work stealing机制,从全局偷取
- 注意获取的过程中需要加锁和解锁
- go线程的简单案例
package main
import (
"fmt"
"time"
)
func newTask() {
i := 0
for {
i++
fmt.Printf("new Goroutine:i = %d\n", i)
time.Sleep(1 * time.Second)
}
}
func main() {
//创建一个go程,去执行newTask()流程,来达到一种并发的效果
go newTask()
i := 0
for {
i++
fmt.Printf("main Goroutine:i = %d\n", i)
time.Sleep(1 * time.Second)
}
}
- 运行结果如图所示
-
主线程和子线程会一直循环下去
-
goexit函数的使用
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
//退出当前goroutine
runtime.Goexit()
fmt.Println("B")
}()
fmt.Println("A")
}()
for {
time.Sleep(1 * time.Second)
}
}
- 运行结果如图所示
五.go用channel来进行两个进程之间的相互通信
- channel <- value //发送value到channel
- <- channel //接收并将其丢弃
- x := <-channel //从channel中接收数据,并将其赋值给x
- x,ok := <-channel //功能同上,同时检测通道是否关闭或者是否为空
- 代码实验
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
defer fmt.Println("goroutine结束")
fmt.Println("goroutine 正在运行")
c <- 666 //将666发送给c
}()
num := <-c
fmt.Println("num=", num)
fmt.Println("main goroutine 结束...")
}
- 结果为
- 这样就通过c通道实现了主线程和子线程的通信
- 如果主线程先读到了num := <-c 那么就会发生堵塞,等待子线程通过channel把值传过来,然后主线程才可以读取到channel中的数值。同理如果子线程先把值传送到channel,也会 发生堵塞,等待主线程来读取channel里面的值,然后才能进行执行。无形之中达到了同步的效果。
六. channel中的缓存介绍
- 第一步,在右侧goroutine正在从通道接收一个值
- 第二步,右侧的这个goroutine独立完成接收动作,而左侧的goroutine正在发送一个新的值到通道里
- 第三步,左侧的goroutine还在向通道里发送新值,右侧的goroutine正在从通道接收另一个值。这个步骤里的两个操作不是同步的,也不会相互阻塞。
- 第四步,所有的发送和接收都完成后,通道里面还有几个值,也有一些空间可以存储更多的值