Go 语法笔记 04 | 青训营

89 阅读3分钟

本篇笔记是笔者在第六届字节跳动青训营期间学习 Go 语言的语法笔记,主要内容为管道的阻塞机制

管道 channel 的阻塞机制

1. 机制介绍

· 管道是本身是安全的,go 中通过管道的阻塞机制来实现协程之间的同步,即通过管道彼此传递讯息。笔者自己的理解是:想象不同的协程是不同的工作者,主函数为任务分发者,分发者将任务放在一个统一的地点(即协程),由不同的协程进行认领,认领后(将数据从管道中取走)进行计算,当完成任务后,再将结果放到约定好的地点(约定好的管道,没有其他特殊要求)。若该协程无任务可以工作,需要退出,再向约定好的地点放置标志,即可退出。相关人员(主函数或者其他协程)检查后可获悉协程的工作状况。

· 在上述工作的过程中,可以发现其实在管道的视角,仅仅有写者和读者(工作者,分发者之类的概念都是由程序设计者赋予的)。又因为每个协程何时会开始工作,工作什么内容都是不可知的,因此就出现了阻塞机制。简单来讲,有以下两个层面:

  • 若管道发现自己已经没有足够的空间,它会让准备向其写入的写者阻塞。直到有读者取出数据(提供空间)后,它会唤醒所有被阻塞的写者,其又可以开始进行正常工作

  • 若管道发现自己已经没有数据供读者去取出,它会让准备向其取出数据的读者阻塞。直到有写者写入数据(提供数据)后,它会唤醒所有被阻塞的读者,其又可以开始进行正常工作

    有趣的是,go 底层会对管道进行检测,若该管道只有写者而没有读者,或者是只有读者没有写者,其会报出死锁错误。(笔者理解这种情况没有必要使用管道,因为这种操作本身就没有意义)

2.案例设计与实现

· 下面是使用协程计算 1~n(n 在程序中为 80000)中的素数,具体代码与相关注释如下:

package main
​
import "fmt"func putNum(intChan chan int, n int) {
    for i := 1; i <= n; i++ {
        intChan <- i
    }
    close(intChan)
}
​
func calPrime(intChan, primeChan chan int, exitChan chan bool) {
    for {
        num, ok := <-intChan
        if !ok {
            break
        }
        isPrime := true
        for i := 2; i < num; i++ {
            if num%i == 0 {
                isPrime = false
                break
            }
        }
        if isPrime {
            primeChan <- num
        }
    }
    fmt.Println("this process has finished!")
    exitChan <- true
}
​
func main() {
    n, intChanSize, processNum := 80000, 2000, 4
    intChan := make(chan int, intChanSize)
    primeChan := make(chan int, n)
    exitChan := make(chan bool, 4)
​
    go putNum(intChan, n) // task allocatorfor i := 0; i < processNum; i++ {
        go calPrime(intChan, primeChan, exitChan)
    }
​
    go func() { // supervisor for process finished
        for i := 0; i < processNum; i++ {
            <-exitChan
        }
        close(exitChan)
        close(primeChan) // all primes are completed
    }()
    cnt := 0
    for {
        prime, ok := <-primeChan
        if !ok {
            break
        }
        fmt.Printf("prime[%v]: %v\n", cnt, prime)
        cnt++
    }
}
​