Go语言进阶 & 工程实践 | 青训营笔记

155 阅读4分钟

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

一、本堂课重点内容

  • 语言进阶:从并发编程的视角了解Go高性能的本质
  • 依赖管理:了解Go语言依赖管理的演进路线
  • 测试:从单元测试实现出发,提升质量意识
  • 项目实战:通过项目需求、需求拆解、逻辑设计、代码实现感受真实的项目开发

二、详细知识点介绍

1、语言进阶——并发编程

并发 VS 并行

并发:多线程程序在一个核的cpu上运行 并行:多线程程序在多个核的cpu上运行

Goroutine

协程 :用户态,轻量级线程,栈MB级别

线程:内核态,线程跑多个协程,栈KB级别

image.png

协程例子

快速打印hello goroutine:0~hello goroutine:4】

package main

import (
   "fmt"
   "time"
)

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

func HelloGoRoutine()  {
   for i := 0; i<5; i++ {
      go func(j int) {
         hello(j)
      }(i)
   }
   time.Sleep(time.Second)
}

func main()  {
   HelloGoRoutine()
}

运行结果:

image.png

CSP (Communicating Sequential Processes)

通过通信共享内存 通过共享内存实现通信(推荐)

Channel

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

无缓冲通道 make(chan int)

有缓冲通道 make(chan int, 2)

使用例子

A 子协程发送 0 - 9 数字

B 子协程计算输入数字的平方

主协程输出最后的平方数

package main

func main() {
   src := make(chan int)
   dest := make(chan int, 3)

   go func() {
      defer close(src)
      for i := 0; i < 10; i++{
         src <- i
      }
   }()
   go func() {
      defer close(dest)
      for i := range src{
         dest <- i *i
      }
   }()
   for i := range dest{
      println(i)
   }
}

运行:

image.png

并发安全 Lock

假如:对变量执行2000次+1操作,5个协程并发执行

不加锁时:结果输出的是未知的(并发安全问题!)

加锁时:结果输出正确

sync

waitgroup

实现并发安全操作和协程间的同步

2、依赖管理

GOPATH

  • bin:项目编译生成的二进制文件

  • pkg:项目编译的中间产物,加速编译

  • src:项目源码

弊端:无法实现package的多版本控制

Go Vendor

通过每个项目引入一份依赖的副本,解决多个项目需要同一个package依赖的冲突问题

当vender目录下没有相关依赖时,再到gopath中去找

弊端:无法控制依赖的版本,更新项目又可能出现依赖冲突,导致编译出错

Go Module

通过go.mod文件管理依赖包版本,通过go get/go mod指令工具管理依赖包

3、测试

image.png 从上到下,覆盖率逐层变大,成本却逐层降低

单元测试

image.png

规则:

  • 所以测试文件以_test.go结尾
  • func TestXxx(*testing.T)
  • 初始化逻辑放到TestMain中

覆盖率:

  • 一般覆盖率: 50%一60%,较高覆盖率80%+
  • 试分支相互独立、全面覆盖
  • 测试单元粒度足够小,函数单一职责

Mock测试库

基准测试

  • 优化代码,需要对当前代码分析
  • 内置的测试框架提供了基准测试的能力

4、项目实践

需求描述

社区话题页面

√ 展示话题 (标题,文字描述)和回帖列表

√ 暂不考虑前端页面实现,仅仅实现一个本地web服务

√ 话题和回帖数据用文件存储

需求用例

image.png

设计ER图

image.png

分层结构

image.png

数据层:数据 Model,外部数据的增删改查

逻辑层:业务 Entity,处理核心业务逻辑输出

视图层: 视图 view,处理和外部的交互逻辑

组件工具

Respository

例子:

var(
    topicIndexMap map[int64]*Topic
    postIndexMap  map[int64][]*Post
) 

Respository-index

初始化话题数据索引例子:

func initTopic IndexMap(filePath string) error {
    open,err := os.Open(filePath + "topic"
    if err != nil{
        return err
    }
    scanner := bufio.NewScanner(open)
    topicTmpMap := make(map[int64]*Topic)
    for scanner.Scan() {
        text := scanner.Text(0var topic Topic
        if err := json.Unmarshal([]byte(text), &topic); err != nil {
            return err
        }
        topicTmpMap[topic.Id] = &topic
        }
    topicIndexMap = topicTmpMap
    return nil
 }

Respository-查询

例子: 索引:话题ID

数据:话题

type Topic struct {
    Id         int64  `json:"id"`
    Title      string `json:"titler"`
    Content    string `json:"content"`
    CreateTime int64  `json:"create_time"`
}
type TopicDao struct {
}
var(
    topicDao *TopicDao
    topicOnce sync.Once
)
func NewTopicDaoInstance() *TopicDao {
    topicOnce.Do(
    func() {
    topicDao = &TopicDao{}
    })
    return topicDao
}
func (*TopicDao) QueryTopicById(id int64) *Topic {
    return topicIndexMap[id]
}

Service

image.png

代码流程编排

image.png

Controller

构建View对象

业务错误码

image.png

Router

初始化数据索引

初始化引擎配置

构建路由

启动服务

image.png

运行

go run server.go

三、课后个人总结

项目实战中了解到各个部分的项目分工,代码拆解的思路很好,我们做项目时可以将大需求化小需求来分析。

四、课程链接学习

Go 语言进阶与依赖管理 - 掘金 (juejin.cn)

Go 语言工程实践之测试 - 掘金 (juejin.cn)

五、其他阅读参考学习

1.锁Lock

2.线程同步 WaitGroup

3. Go Module