GO语言上手——工程实践|青训营笔记

124 阅读4分钟

01.语言进阶

从并发编程的视角带大家了解GO高性能的本质

01.并发 VS 并行

并发:时间片切换实现(单核)

并行:多核cpu上运行(go充分发挥多核优势) image.png

1.1 Goroutine 协程

go实现高并发的手段

通过对比线程来理解协程

image.png 线程为内核态,创捷切换停止属于很重的系统操作

协程则为用户态,轻量级,创建和调度由GO语言本身完成

GO语言一次可创建上万的协程,GO语言更适合高并发的原因所在 协程的开启,在函数前加上一个GO关键字

eg:

func hello(i int) {
   fmt.Println("hello goroutine :" + fmt.Sprint(i))
}

func main() {
   for i := 0; i < 5; i++ {
      go func(j int) {             //go 关键字开启协程
         hello(j)
      }(i)
   }
   time.Sleep(time.Second)       //用sleep完成阻塞,保证子线程在完成前,主线程不退出
}

输出结果(乱序是通过并行的打印输出):

image.png

1.2 CSP(Communicating Sequenential Processes)通讯顺序过程

go 提倡通过通信共享内存(下图左), 而不是通过共享内存实现通信(下图右)

image.png

一个协程与另一个协程见通信

通过通道:先入先出通过Channel(见1.3)

通过临界区:通过加锁(见1.4)完成临界区访问问题

1.3Channel 渠道

创建通过make关键字

make(chan 元素类型,[缓冲大小])

  • 无缓存通道 make(chan int)
  • 有缓存通道 make(chan int,2)

image.png 无缓存通道又本成为同步通道,解决同步问题的方式就是使用有缓冲通道(典型的生成消费模型·操作系统知识点)

func CalSquare() {
   src := make(chan int)  //A,B协程的通信
   dest := make(chan int, 3)  //生成消费不均衡
   //生产者 子协程A发送0~9数字
   go func() {
      //关闭
      defer close(src)
      for i := 0; i < 10; i++ {
         src <- i
      }
   }()
   //子协程B计算输入数字的平方
   go func() {
      defer close(dest)
      for i := range src {
         dest <- i * i
      }
   }()
   //消费者 主协程输出最后的平方数
   for i := range dest {
      //复杂操作
      println(i)
   }
}

image.png 顺序打印,安全性验证

1.4并发安全Lock

go也提供通过临界区通信

image.png 不加锁有一定概率产生错误结果,开发中避免对共享内存进行非并发安全的读写操作

1.5 WaitGroup

以上代码用sleep阻塞,这里我们介绍WaitGroup实现并发任务同步

image.png 计数器为0则所有任务完成

对1.1代码优化(快速打印)


func ManyGoWait() {
   var wg sync.WaitGroup
   wg.Add(5) //五个hello,所以数量设置五
   for i := 0; i < 5; i++ {
      go func(j int) {
         defer wg.Done() //执行完成后-1
         hello(j)
      }(i)
   }
   wg.Wait() //主协程阻塞直到计数器为0的时候并发执行
   //time.Sleep(time.Second)
}

快速打印结果: image.png

1.5 小结

  • Goroutine 协程的高效调度
  • Channel 通过通信实现共享内存
  • Sync 包下关键字lock,GoWait,实现并发安全操作和携程间同步

02.依赖管理

了解GO语言依赖管理的演进路线

image.png 学会站在巨人的肩膀上

用经过验证的组件工具,来提升自己的研发效率

  • 工程项目不可能基于标准库0~1编码搭建
  • 学会使用管理依赖库,更多精力放在项目上

image.png

2.1 Go 依赖管理演进

  • 不同环境(项目)依赖的版本不同
  • 控制依赖库的版本

image.png

2.1.1 GOPATH

GO的工作区

filename用途
bin项目编译的二进制文件
pkg项目编译的中间产物,加速编译
src项目源码

项目代码直接依赖src下的代码

go get 下载最新版本的包到src目录下

GOPATH——弊端

无法实现package多版本控制 image.png V2可能把A函数删掉了,解决方法增加Go Vendor

2.1.2 Vendor

通过每个项目引入一份依赖的副本vendor image.png

GO Vendor——弊端

无法清晰标识依赖版本的概论

image.png 解决方法引入 Go Module

2.1.2 Module

依赖配置——go.mod

image.png 唯一定位每个版本的每次提交

依赖配置——version

版本规则一:语义化版本

image.png MAJOR:大版本,不同major见可不兼容,代码隔离

MINOR:新增函数功能,需要前后兼容

PATCH:bug修复

版本规则二:基于 commit 伪版本

image.png 版本前缀,同语义化+时间戳+12位哈希校验码

依赖配置——indirect

image.png 用indirect标识非直接依赖

依赖配置——incompatible

标识可能会存在不兼容代码逻辑 image.png

依赖配置——依赖图

选择满足本次构建的最低版本

image.png

依赖分发——回源

image.png

依赖分发——Procy

服务站点,缓存原站中的内容,保持依赖的稳定性

image.png

依赖分发——变量GOPROXY

前面两个站点都没有依赖的话,会回到第三方代码平台上

image.png 项目查找依赖路径

工具 go get 和 go mod

image.png

image.png 运行前可以先tidy一下,减少构建时间

2.1.3小结

image.png

image.png

03.测试

从单元测试实践出发,提升大家的质量意识

3.1单元测试

image.png

3.1.1 单元测试——规则

image.png

04.项目实战

通过项目需求、需求拆解、逻辑设计、代码实现带大家感受下真实的项目开发

4.1.1 需求设计

需求背景:社区话题页码

image.png

4.1.2 需求用例

实体:话题+帖子

image.png 抽象出topic和postlist结构体

4.1.3 ER图——Entity Relationship Diagram

image.png 一对多关系

4.1.4 分层结构

  • 数据层:数据Model,外部数据的增删改查
  • 逻辑层:业务Entity,处理核心业务逻辑输出
  • 视图层:视图view,处理和外部的交互逻辑

image.png

4.1.5 组件工具

  • gin 高性能 go web 框架

github.com/gin-gonic/g…

  • Go Mod go mod init

go get gopkg.in/gin-gonic/gin.v1@v1.3.0

如果显示失败在终端执行:

go env -w GOSUMDB=off

go env -w GO111MODULE=on

go env -w GOPROXY=goproxy.cn,direct

再尝试go get gopkg.in/gin-gonic/gin.v1@v1.3.0

4.2 代码开发

4.3 测试运行