golang学习笔记7 | 青训营

76 阅读2分钟

goroutine和通道

goroutine

golang中,每一个并发执行的活动称为goroutine。当一个程序启动时,只有一个goroutine来调用main函数,称它为主goroutine。新的goroutine通过go语句进行创建。goroutine与通道channel支持CSP(通信顺序进程)模型。

  • 语法上,一个go语句是在普通的函数或者方法调用前加上go关键字前缀。
  • main函数返回,当它发生时,所有的goroutine都暴力地直接终结,然后程序退出。

通道

通道是可以让一个goroutine发送特定值到另一个goroutine的通信机制。每一个通道是一个具体类型的导管,叫做通道的元素类型。一个有int类型元素的通道写为chan int

ch := make(chan int) // ch 的类型是`chan int`

通道是一个使用make创建的数据结构的引用。当复制或者作为参数传递到一个函数是,复制的是引用,这样调用者和被调用者都引用同一份数据结构。和其他引用类型一样,通道的零值是nil

  • 通道有两个主要操作:发送(send)和接收(receive),两者统称为通信
ch <- x     // 发送语句
x = <- ch   // 赋值语句中的接收表达式
<- ch       // 接收语句,丢弃结果
  • 通道支持第三个操作:关闭(close),它设置一个标志位来指示值当前已经发送完毕。关闭后的发送操作将导致宕机。在一个已经关闭的通道上进行接受操作,将获取所有已经发送的值,直到通道为空。
close(ch)
  • 通道还分为无缓冲通道和缓冲通道。
ch = make(chan int)     // 无缓冲通道
ch = make(chan int, 0)  // 无缓冲通道
ch = make(chan int, 3)  // 容量为3的缓冲通道

无缓冲通道

无缓冲通道上的发送操作将会阻塞,直到另一个goroutine在对应的通道上执行接收操作,这时值传送完成,两个goroutine都可以继续执行。

  • 使用无缓冲通道进行的通信导致发送和接收goroutine同步化。因此,无缓冲通道也称为同步通道。

管道

通道可以用来连接goroutine,这样一个的输出是另一个的输入。这个叫管道(pipeline)。

  • 没有一个直接的方式来判断是否通道已经关闭,但是这里有接收操作的一个变种,它产生两个结果:接收到的通道元素,以及一个布尔值(通常称为ok)。
  • 通道也是可以通过垃圾回收器根据它是否可以访问来决定是否回收它,而不是根据它是否关闭。(不要将这个close操作和对于文件的close操作混淆。当结束的时候对每一个文件调用Close方法是非常重要的。)

单向通道类型

golang的类型系统提供了单向通道类型,仅仅导出发送或接收操作。如类型chan <- int是一个只能发送的通道,允许发送但不允许接收。反之,类型<- chan int是一个只能接收的int类型通道,允许接收但是不能发送。

  • 在任何赋值操作中将双向通道转换为单向通道都是允许的,但是反过来是不行的。

缓冲通道

缓冲通道上的发送操作在队列的尾部插入一个元素,接受操作从队列的头部移除一个元素。

  • 程序需要知道通道缓冲区的容量,可以通过调用内置的cap函数获取它。
  • 无缓冲通道提供强同步保障,因为每一次发送都需要和一次对应的接收同步;对于缓冲通道,这些操作则是解耦的。

并行循环

  • 完全独立的子问题组成的问题称为高度并行。高度并行的问题是最容易实现并行的,有许多并行机制来实现线性扩展。
  • sync.WaitGroup作为计数器类型,可以被多个goroutine安全地操作,然后有一个办法一直等到它变为0

使用select多路复用

  • time.Tick函数返回一个通道,它定期发送事件,像一个节拍器一样。
  • select可以实现多路复用
select {
    case <- ch1:
        // ...
    case x := <- ch2:
        // ... use x ...
    case ch3 <- y:
        // ...
    default:
        // ...
}

select一直等待,直到一次通信来告知有一些情况可以执行。

  • 如果多个情况同时满足,select随机选择一个,这样保证每一个通道有相同的机会会被选中。