《Go程序设计语言》阅读要点记录

第8章 goroutine和通道

Go有两种并发编程的风格

(1)CSP并发模式:goroutine+channel(如channel)
(2)共享内存多线程的传统模型(如sync.Mutex)
  1. 当一个程序启动时,只有一个goroutine来调用main函数,称之为主goroutine。
  2. main函数返回时,所有的goroutine都暴力的直接终结。
  3. 没有程序化的方法让一个goroutine来停止另一个,但是有办法和goroutine通信来要求它自己停止。
  4. channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
  5. channel是一个使用make创建的数据结构的引用,当复制或作为参数传递到一个函数时,复制的是引用,这样调用者和被调用者都引用同一份数据结构。
  6. 和其他引用一样,channel的零值是nil。
  7. 同种类型的channel可以使用==进行比较,channel也可以和nil进行比较。

发送、接收、关闭

  1. 通道主要有发送接收 两个操作,两者统称为通信。
(1)发送  ch <- data
(2)接收  data = <- ch  或 <- ch (接收的结果不使用而直接丢弃也是允许的)
  1. channel的第3种操作:关闭。
(1)在已经关闭的channel上进行发送操作将导致宕机
(2)在已经关闭的channel上进行接收,将获取所有已经发送的值,直到channel为空
(3)当关闭的channel被读完后,所有后续的接收操作都仍可进行,只是会取到通道元素类型对应的零值
(4)可以使用ok语法来判断channel是否已关闭
data, ok := <- ch
if !ok {
    // 说明channel已关闭且读完
}
(5)为了对(4)中的写法进行简化,可以用range语法循环对channel进行读取,range会不断从channel 接收值,直到它被关闭(close)。
(6)主动关闭channel不是一个必须的操作,channel是可以自动回收的。只有在需要通知接收方goroutine所有的数据都发送完毕的时候,发送方才需要主动关闭channel。
(7)GC是根据channel是否可以访问来决定是否回收它的,而不是根据它是否关闭。
(8)关闭一个已经关闭的channel会导致宕机,关闭一个空channel(nil)也会导致宕机。
(9)关闭channel还可以作为一个广播机制。

无缓冲通道和缓冲通道

ch = make(chan int)  //无缓冲通道
ch = make(chan int, 0)  //无缓冲通道
ch = make(chan int, 3)  //容量为3的缓冲通道
  1. 无缓冲通道的发送和接收会强制同步化,又称为同步通道。
  2. 并发并不意味着事件一定同时发生,而是不能假设事件的先后顺序。
  3. 缓冲通道的缓冲区,可以将发送和接收goroutine进行解耦。
  4. 如果程序要知道通道的容量,可以使用内置的cap函数:

fmt.Println(cap(ch))

  1. 如果要获取当前通道内的元素个数,可以使用内置的len函数:

fmt.Println(len(ch))
因为在并发程序中这个信息会随着检索操作很快过时,所以它的价值很低,但是它在错误诊断和性能优化的时候很有用。

  1. 粗暴的将缓冲通道作为队列在单个goroutine中使用是个错误。如果没有另一个goroutine从通道进行接收,发送者(也许是整个程序)有被永久阻塞的风险。如果仅仅需要一个简单的队列,使用slice创建一个即可。
  2. 对于无缓冲的通道,如果因为没有goroutine接收时,会导致多个发送的goroutine被卡住,这叫做 ** goroutine泄露 **,这属于一个bug。不像回收变量,泄露的goroutine不会自动回收,所以确保goroutine在不再需要的时候可以自动结束。

单向通道

  1. 函数的channel形参有时会要求不能发送或不能接收,这样就可以避免在函数内部误用。
  2. chan <- int 是只能发送的int类型通道,<- chan int是一个只能接收的int类型通道。
  3. 函数内违反单向通道使用原则的错误,会在编译时被检查出来。
  4. 由于close操作只能在发送方操作,所以试图关闭一个只能接收的单向通道会引起编译时报错。
  5. 在任何赋值操作中将双向通道转换为单向通道都是允许的,但是反过来是不行的。

并行循环

func test() {
    var wg sync.WaitGroup
    for f := range filenames {
        wg.Add(1)

        //worker
        go func(f string) {
            defer wg.Done()
            // do something
        }(f)
    }

    wg.Wait()
    // do other
}

#第9章 使用共享变量实现并发