Go语言上手-工程实践 | 青训营笔记

77 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第一篇笔记

一. go的依赖管理

  1. 概述:利用已经有的封装好的包来提高开发的效率
  2. 依赖管理的演进
  • gopath
  • go vendor
  • go module
  • 注意:版本的选择,不同环境的依赖版本不同
  1. 依赖配置图

image.png

  • 一个项目即依赖A又依赖B,而A和B又分别依赖了C项目的v1.3和v1.4,最终编译所使用的C项目的版本v1.4,原因是选择最近的兼容版本。
  1. 依赖分发
  • 为了获得一个稳定、可靠的开发环境,选择Proxy,他可以缓存原来网站中的内容,保持版本不变。如图所示

image.png 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 二. 测试
  1. 概述:在开发后进行软件测试可以避免事故的发生
  2. 分类:
  • 回归测试
  • 集成测试
  • 单元测试
  1. 规则
  • 所有测试文件要以_test.go结尾
  • func TestXxx(*testing.T)
  • 初试化逻辑放到TestMain中
  1. 单元测试-mock
  • 快速mock函数
  • 为一个函数打桩
  • 为一个方法打桩
  1. 基准测试
  • 优化代码,需要对当前代码分析
  • 内置的测试框架提供了基准测试的能力 三. 项目实战
  1. 需求分析
  • 实习社区话题页面
  • 展示话题(标题和文字描述)和回帖列表
  • 暂时不考虑前端页面实现,仅仅实现本地web服务
  • 话题和回帖数据用文件存储
  1. 需求用例

image.png

  • E-R图

image.png

  • 软件开发分层结构
  1. 数据层:数据model,外部数据的增删改查
  2. 逻辑层(服务层):业务entity,处理核心业务逻辑输出
  3. 视图层(控制层):视图view,处理和外部的交互逻辑
  • 组件工具
  1. gin高性能go web框架
  2. go mod
  • go mod init
    • go get gopkg.in/gin-gonic/gin.v1@v1.3.0
  • repository

image.png

  • repository-index

![image.png](p6-juejin.byteimg.com/tos-cn-i- k3u1fbpfcp/1f78c212d8104ea8a43f2bff03114821~tplv-k3u1fbpfcp-watermark.image?)

四.goroutine基本模型和调度设计策略

  • 单一执行流程、计算机只能一个任务一个任务处理
  • 进程阻塞所带来的CPU浪费时间
  1. 多线程擦操作系统 - 采用轮寻调度,即用调度器对进程A、进程B、进程C进行轮寻调度

image.png

  • 在做进程之间的切换,线程之间会有切换成本,这些切换成本被称为CPU浪费时间成本,所以进程/线程的数据量越多,切换成本就越大,也就越浪费

image.png

  • 进程/线程消耗内存 image.png
  • 线程的切分

image.png

  • go最适合的用多对多的关系

image.png

  • goland对协程的处理

image.png

  • golang对灵活调度的处理
    1. 旧的调度器,缺点:激烈的锁竞争、延迟和额外的系统负载、增加了系统开销 image.png
  • goland对调度器的改进 image.png
  • 总体思想 image.png
  • work stealing偷取机制

image.png

  • hand off机制

image.png

  • 利用并行

image.png

  • 抢占 image.png
  • 全局G队列
    • work stealing机制,从全局偷取
    • 注意获取的过程中需要加锁和解锁

image.png

  • 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)
   }
}
  • 运行结果如图所示

image.png

  • 主线程和子线程会一直循环下去

  • 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)
   }
}
  • 运行结果如图所示

image.png

五.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 结束...")
}
  • 结果为

image.png

  • 这样就通过c通道实现了主线程和子线程的通信
  • 如果主线程先读到了num := <-c 那么就会发生堵塞,等待子线程通过channel把值传过来,然后主线程才可以读取到channel中的数值。同理如果子线程先把值传送到channel,也会 发生堵塞,等待主线程来读取channel里面的值,然后才能进行执行。无形之中达到了同步的效果。

六. channel中的缓存介绍

  • 第一步,在右侧goroutine正在从通道接收一个值
  • 第二步,右侧的这个goroutine独立完成接收动作,而左侧的goroutine正在发送一个新的值到通道里
  • 第三步,左侧的goroutine还在向通道里发送新值,右侧的goroutine正在从通道接收另一个值。这个步骤里的两个操作不是同步的,也不会相互阻塞。
  • 第四步,所有的发送和接收都完成后,通道里面还有几个值,也有一些空间可以存储更多的值