Go并发编程 | 青训营笔记

106 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天。主要学习了Go的并发编程、依赖管理和测试。并发编程主要包括了goroutine、channel、lock、WaitGroup 的使用。依赖管理包括GOPATH、Go Vender、Go Module。测试包括单元测试、Mock测试、基准测试。

1.并发编程

(1)goroutine

Go语言内置了调度和上下文切换的机制。在Go语言并发编程中,只需要把一个任务包装成一个函数,开启一个goroutine去执行这个函数就可以了
Go语言在main函数中会启动一个主线程,一个Go语言线程上可以起多个协程,协程是轻量级的线程

Go语言协程特点:

  • 有独立的栈空间
  • 共享程序堆空间
  • 调度由用户控制
  • 协程是轻量级的线程

使用goroutine非常简单,只需要在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine

func main() {
    for i := 0; i < 5; i++ {
        //启动一个协程去完成打印
        go func(j int) {
                hello(j)
        }(i)
    }
    time.Sleep(time.Second)
}

在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。 当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束.因此,这里为了让循环里面的goroutine执行完成,使用time.Sleep()暂停等待。

(2) channel

Go语言的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。channel的主要作用就是实现goroutine之间的通信

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

通道是引用类型,通道类型的空值是nil。声明的通道后需要使用make函数初始化之后才能使用。创建channel的格式如下:

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

通道有发送(send)、接收(receive)和关闭(close)三种操作。发送和接收都使用<-符号。下面是一个使用通道进行发送和接收的例子。

func CalSquare() {
    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)
    }
}

本例中使用了一个无缓冲的channel和一个有缓存的channel。
无缓冲的channel只有在有对象接收值的时候才能发送值,使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。
有缓冲的channel利用缓冲区可以解决生产、消费速度不匹配的问题。

(3) 互斥锁lock

var (
    x    int64
    lock sync.Mutex
)

func addWithLock() {
    for i := 0; i < 2000; i++ {
        lock.Lock()
        x += 1
        lock.Unlock()
    }
}

不加锁会出现并发安全问题,出现未预料的结果

(4) WaitGroup 计数器

实现并发任务同步 主要方法为Add,Done,Wait

func ManyGoWait() {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i < 5; i++ {
        go func(j int) {
                defer wg.Done()
                hello(j)
        }(i)
    }
    wg.Wait()
}

2.依赖管理

开发项目时引入的各种开发包
(1) GOPATH->Go Vender ->Go Module

(2) GOPATH 环境变量
依赖src下的代码,通过go get下载到src目录 问题:无法实现package的多版本控制

(3) Go Vender
在项目中添加vender的依赖副本 问题:A依赖B和C,B和C的依赖冲突 无法控制依赖版本,更新可能出现依赖冲突,发生编译错误

(4) Go Module
通过go.mod管理依赖包版本 go get/go mod 管理依赖包 终极目标:定义版本规则和管理项目依赖关系

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

依赖冲突解决: 选择最低的依赖兼容版本

3.测试

单元测试 覆盖率

Mock 打桩测试 去除对于文件、数据的强依赖

基准测试 Benchmark开头 测试性能,具体花费时间