Go语言进阶 | 青训营笔记

79 阅读5分钟

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

写在开头

本人是一个第一次参加字节青训营的学生,主要是简单记录一下自己学习的过程以及复习(详细的需要自己去看每天对应的课程),每天会发前一天课程的笔记以及自己的思考(对项目)

GO语言进阶

goroutine

并发编程在当前软件领域是一个非常重要的概念,随着CPU等硬件的发展,我们无一例外的想让我们的程序运行的快一点、再快一点。Go语言在语言层面天生支持并发,充分利用现代CPU的多核优势,这也是Go语言能够大范围流行的一个很重要的原因。

基本概念

首先我们先来了解几个与并发编程相关的基本概念。

串行、并发与并行

串行:我们都是先读小学,小学毕业后再读初中,读完初中再读高中。

并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天)。

并行:同一时刻执行多个任务(你和你朋友都在用微信和女朋友聊天)。

进程、线程和协程

进程(process):程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。

线程(thread):操作系统基于进程开启的轻量级进程,是操作系统调度执行的最小单位。

协程(coroutine):非操作系统提供而是由用户自行创建和控制的用户态‘线程’,比线程更轻量级。

Goroutine 是 Go 语言支持并发的核心,在一个Go程序中同时创建成百上千个goroutine是非常普遍的,一个goroutine会以一个很小的栈开始其生命周期,一般只需要2KB。区别于操作系统线程由系统内核进行调度, goroutine 是由Go运行时(runtime)负责调度。例如Go运行时会智能地将 m个goroutine 合理地分配给n个操作系统线程,实现类似m:n的调度机制,不再需要Go开发者自行在代码层面维护一个线程池。

go f()  // 创建一个新的 goroutine 运行函数f

go func(){ // 匿名函数
  // ...
}()

channel

单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。

虽然可以使用共享内存进行数据交换,但是共享内存在不同的 goroutine 中容易发生竞态问题。为了保证数据交换的正确性,很多并发模型中必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。

Go语言采用的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信

如果说 goroutine 是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。

Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。

var ch1 chan int   // 声明一个传递整型的通道
var ch2 chan bool  // 声明一个传递布尔型的通道
var ch3 chan []int // 声明一个传递int切片的通道

Go依赖

Go mod

以前的Go是用GoPath的,项目直接依赖src下的代码,get也是直接下载到src,它的弊端是如果A依赖B的某一个不同版本,无法实现多版本控制。 后面有了Go vendor,通过每个项目引入一个依赖的副本,解决了多个项目需要同一个包依赖的冲突问题。 但是问题是无法控制依赖的版本,新的项目可能会有依赖冲突,导致编译出错。

于是出现了Go mod管理依赖包版本,gomod系统中的定义可以直接对应到多版本代码管理(github)中某一个提交或者版本,可以直接下载指定依赖,完成依赖分发,但是会有以下问题。

无法保证稳定性,无法保证依赖可用性,增加第三方压力。

而go proxy可以解决这些问题,他是一个服务的站点,会缓存软件内容,缓存的版本不会变,并且会在原站软件删除后依然可以用,构建直接从proxy拉取依赖。

通过goproxy环境变量使用,direct表示源站,优先从proxy1下载,找不到往后面找,都不存在会直接回到源站直接下载以来。

单元测试

我们通过测试功能来校对期望值,来保证质量和提高效率,测试文件要以_test.go结尾,函数则是Test开头,链接的第一个字母大写。比如

package main

import "testing"

func HelloTom() string {
    return "lzj"
}

func TestHello(t *testing.T) {
    output := HelloTom()
    exectOut := "Tom"
    if output != exectOut {
        t.Errorf("错误")
    }
}

依赖测试

工程中复杂的项目,一般会依赖很多东西,单测需要保证稳定和幂等,稳定(相互隔离,可以在任何时间,环境进行测试)幂等是每一次测试都应该产生于之前一样的结果,实现这个目的要用mock机制。

基准测试

它是以Benchmark开头,单元是Tes,它的参数是*testing.B。