Go语言工程实践入门(一)协程和并发 | 青训营

27 阅读4分钟

并发与同步

Go 协程(Goroutine)

并行强调多核,多线程程序在多个核上运行,而并发狭义是指利用时间片等操作使多个线程在一个核的cpu上运行,而并行也可以叫做广义的并发,Go 可以充分发挥多核优势,高效运行。

Go 协程(Goroutine)在用户态,是轻量级线程,栈是 KB 级别,而线程是内核态,一个线程可以跑多个协程,线程的栈是 MB 级别。

启动 Go 协程的方法是在函数调用之前加一个 go 关键字,下面是一个例子。

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

ManyGo 函数是使用 sync.WaitGroup 实现协程并发的示例。

首先创建了一个 sync.WaitGroup 类型的变量 wgWaitGroup 用于等待一组协程完成执行。

然后,通过 for 循环创建了 5 个协程。在循环内部,首先调用 wg.Add(1) 来增加 WaitGroup 的计数器,表示有一个协程正在执行。然后使用 go 关键字开启一个匿名函数作为协程。

在匿名函数中,我们使用 defer wg.Done() 来在函数执行完毕后减少 WaitGroup 的计数器,表示一个协程已经完成执行。然后调用 hello(j) 函数,将传递进来的 j 参数作为参数传递给 hello 函数,这个匿名函数的实际参数是循环变量 i

通过使用 sync.WaitGroup,可以确保在所有协程执行完毕之前,程序会一直等待,调用 wg.Wait() 来阻塞主程序,直到所有协程都完成执行。

这样,当调用 ManyGo() 函数时,5 个协程并发执行 hello 函数,每个协程拥有不同的 j 值,并且会等待所有协程执行完毕后才结束。

通道(Channel)

Go 语言通过通信共享内存,同时也保留了通过共享内存实现通信的方法,不过使用共享内存需要使用锁(Lock)。

两个协程通过通信共享内存,需要使用通道,Go 可以创建有缓冲通道和无缓冲通道。make(chan 元素类型, [缓冲大小]),下面是一个例子。

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)
    }
}

CalSquare 函数是一个使用通道实现的示例,用于计算一组数字的平方值。

首先创建了两个通道 srcdest。其中 src 通道用来发送原始数字,dest 通道用来接收计算后的平方值。注意,dest 通道的缓冲区大小设置为 3,表示可以同时存储最多 3 个平方值。

然后,通过 go 关键字开启了两个匿名函数作为协程。第一个匿名函数使用循环将数字从 0 到 9 发送到 src 通道中,并在发送完毕后关闭 src 通道。第二个匿名函数使用 range 循环从 src 通道中接收数字,并计算它们的平方值,然后将结果发送到 dest 通道中,并在处理完所有数字后关闭 dest 通道。

接下来,在主程序中使用 range 循环从 dest 通道中接收计算后的平方值,并将其打印输出。

这样,当调用 CalSquare() 函数时,程序并发地计算一组数字的平方值,并按顺序输出结果。由于 dest 通道设置了缓冲区,可以同时接收多个平方值,使得计算和接收操作可以并行进行。

同步(Sync)

为了保证并发安全需要做到同步,有两个方式。

  1. 使用 Lock 锁

    在 Go 语言中,使用 sync.Mutex 类型的变量可以创建一个互斥锁。通过调用锁的 Lock 方法可以获取锁,防止其他 Goroutine 进入被保护的代码区域。在代码执行完毕后,通过调用锁的 Unlock 方法释放锁,使得其他 Goroutine 可以获取锁并进入该区域。

  2. 使用 WaitGroup

    通过使用 sync.WaitGroup,可以确保在所有协程执行完毕之前,程序会一直等待,详见协程部分的函数例子。

    • var wg sync.WaitGroup 创建计数器变量
    • wg.Add(delta int) 计数器增加 delta,表示增加 delta 个协程
    • wg.Done() 计数器减 1
    • wg.Wait() 阻塞直到计数器为 0

Go module

首先打开 module 模式,这会摒弃掉 GOPATHvendor,从 1.13 开始,默认开启 module 模式

go env -w GO111MODULE=on

然后设置GOPROXY

go env -w GOPROXY=https://goproxy.cn,direct

go mod 相关命令

go mod
The commands are:
  download    download modules to local cache (下载依赖的module到本地cache)
  edit        edit go.mod from tools or scripts (编辑go.mod文件)
  graph       print module requirement graph (打印模块依赖图)
  init        initialize new module in current directory (在当前文件夹下初始化一个新的module, 并创建go.mod文件)
  tidy        add missing and remove unused modules (增加缺失module,去掉无用module)
  vendor      make vendored copy of dependencies (复制依赖到vendor)
  verify      verify dependencies have expected content (校验依赖内容)
  why         explain why packages or modules are needed (解释为什么需要依赖或模块)