并发、依赖、测试|青训营笔记

121 阅读3分钟

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

并发编程

并发&并行

  • 并发

    • 并发是多个线程同时进行
    • 单核处理多线程的模式下各个线程交替进行
  • 并行

    • 并行是多个线程同一时刻同时进行
    • 多核处理多线程可以进行并行
  • 特点:

    • 并发不一定并行
    • 并行可以充分调动多核计算机的性能

Coroutine

Coroutine:协程

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

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

CSP

Communicating Sequential Processes

协程间的通信可以有两种方式:

  • 通过通信共享内存
  • 通过共享内存实现通信
  • 在go中提倡的是前者

Channel

通信通道

通信通道有两种:有缓冲通道和无缓冲通道

func main() {
   src := make(chan int)
   dest := make(chan int, 3)
   go func() {
      defer close(src)
      for i := 0; i < 20; i++ {
         fmt.Println(i, "被造成来了!")
         src <- i
      }
   }()
   go func() {
      defer close(dest)
      for i := range src {
         dest <- i
      }
   }()
   for i := range dest {
      fmt.Println(i, "被消费了!")
   }
}

Lock

并行安全

进行通过共享资源通信的时候,应当实现Lock保证并发安全

var(
    x int64
    lock sync,Mutex
)
​
func addWithLock(){
    for i:= 0; i < 2000;i++{
    lock.Lock()
    x+=1
    lock.Unlock()
    }
}

WaitGroup

计数器

当开启线程的时候+1;

执行结束-1;

主线程阻塞知道计数器为0;

func hello(i int) {
   fmt.Println("hello goroutine:", i)
}
func main() {
   var waitGroup sync.WaitGroup
   waitGroup.Add(5)
   for i := 0; i < 5; i++ {
      go func(j int) {
         defer waitGroup.Done()
         hello(j)
      }(i)
   }
   waitGroup.Wait()
}

依赖管理

为了更多的关注业务逻辑的实现,我们不可能基于标准库进行0~1编码搭建项目,像是涉及框架、日志、driver、以及collection等一系列依赖都会通过sdk的方式引入,这样对依赖包的管理就显得尤为重要

演进路线

GOPATH->GO Vendor->Go Module

不同环境(项目)依赖的版本不同

因此需要控制依赖库的版本

GOPATH

GOPATH是Go语言支持的一个环境变量

value是Go项目的工作区。 目录有以下结构:

  • src:存放Go项目的源码;
  • pkg:存放编译的中间产物,加快编译速度;
  • bin:存放Go项目编译生成的二进制文件

在这种管理方式下,无法实现package的多版本控制

Go Vendor

Vendor 是当前项目的一个目录,其中存放了当前项目的依赖副本

如果当前项目存在Vendor目录,会优先使用该目录下的依赖,如果依赖不存在会去GOPATH中寻找

寻址:vendor =》 GOPATH

但是此时仍然无法控制依赖的版本,更新项目困难出现依赖冲突

Go Module

  • 使用go.mod 文件管理依赖包的版本
  • 通过go get/go mod指令工具来管理依赖包
  • 类似于Java中的maven

依赖管理的三要素:

  • 配置文件,描述依赖 go.mod
  • 中心仓库管理依赖 Proxy
  • 本地工具 go get/mod

依赖配置

go.mod

模块路径标识一个模块,从中可以看到从哪里找到此模块

依赖包的源码由GitHub托管,如果项目的子包向北单独引用,则需要单独的init go.mod 文件进行管理

下面的是原生依赖的sdk的版本

最下面是单元依赖,每个依赖单元由模块路径+版本来唯一标识

Version

  • 语义化版本

    • ${MAJOR}.${MINOR}.${PATCH}
    • v1.2.0
  • 基于commit伪版本

    • vx.0.0-yyyymmddhhmmss-abcdefgh1234
    • v0.0.0-20220401081311-c38fb59326b7

indirect

下面我们再来看下依赖单元中的特殊标识符,首先是indirect后缀,表示go.mod对应的当前模块,没有直接导入该依赖模块的包,也就是非直接依赖,标示间接依赖,例如

incompatible

主版本2+模块会在模块路径增加/N后缀

对于没有go.mod文件并且主版本在2或者以上的依赖,会在版本号后加上+incompatible后缀

依赖图

在这图中,编译时使用的C项目的版本为V1.4,因为会选择一个最低版本的均兼容的版本

依赖分发

Proxy

Go Proxy是一个服务站点,使用它之后,构建是会直接从Go Proxy站点拉取依赖

GOPROXY

GOPROXY="https://proxy1.cn,https://proxy2.cn ,direct” GOPROXY=“https://proxy1.cn,https://proxy2.cn,Direct”

服务站点的URL列表,“direct”表示源站

寻址次序为Proxy1-> Proxy2-> Direct

工具

  • go get

  • go mod

测试

单元测试

基本规则

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

覆盖率

覆盖率表示在测试中由多少比例行数的代码被使用到了

测试分支应当相互独立、全面覆盖

测试单元粒度要小,函数保证单一职责

基准测试

基准测试测定程序运行时的性能以及消耗CPU的程度