Go并发channel

16 阅读15分钟

37703db61617c80afe2c966d3d72bef.jpg

channel的基本概念:

Go语言中,channel既指通道类型.也指代可以传递某种类型值的通道.通道即某一个通道类型的值.是该类型的一个实例.

类型表示法:

与切片类型和字典类型相同.通道类型也属于引用类型.一个泛化的通道类型的声明应该这样.

chan T

关键字chan是代表了通道类型的关键字.而T则表示了该通道类型的元素类型.通道类型的元素类型.限制了可以经由此类通道传递的元素的值类型.可以声明一个别名类型.

type IntChan chan int 

别名类型IntChan代表了元素类型为int的通道类型.例如.可以直接声明一个chan int 类型的变量.

var intChan chan int

在初始化之后.变量intChan就可以用来传递int类型的元素值了.

只用于发送值的通道类型的表示:

chan<-T

接收元素值的通道类型表示:

<-chan T

值表示法:

通道类型是一个引用类型.所以一个通道类型的变量在被初始化之前.,其值一定是nil.这也是此类型的零值.

与其他类型不同.通道类型的变量是用来传递值的.而不是存储值的.所以.通道类型并没有对应的值表示法.它的值具有即时性.是无法用字面量来准确表达的.

操作的特性:

通道是在多个goroutine之间传递数据和同步的重要手段.而对通道的操作本身也是同步的.在同一时刻.仅有一个goroutine能向一个通道发送元素值.同时也仅有一个goroutine能从它那里接收元素值..在通道中.各个元素值都是严格按照发送到此的先后顺序排列的.最早被发送至通道的元素会最先被接收.因此.通道相当于一个FIFO(先进先出的)消息队列.通道中的元素值都具有原子性.是不可被分割的.通道中的每一个元素值都只可能被某一个goroutine接收.已被接收的元素值会立刻从通道中删除.

初始化通道:

引用类型的值都需要使用内建函数make来初始化.通道类型也不例外.

make(chan int.10)

这个表达式初始化了一个通道类型的值.传递给make函数的第一个参数表明.此值的具体类型是元素类型为int的通道类型.而第二个参数则指该通道值在同一时刻最多可以缓冲10个元素值.

初始化一个通道的时候省略第二个参数值.

make(chan int)

一个通道的缓冲容量总是固定不变的.如果第二个参数值被省略了.则表示被初始化的这个通道永远无法缓冲任何元素值.发送给它的元素值应该被立刻取走.否则发送方的goroutine就会被暂停(或者说阻塞).直到有接收方接收这个元素值.

接收元素值:

接收操作符<-不但可以作为通道类型声明的一部分.也可以用于通道操作(发送或接收元素值).

strChan:=make(chan string,3)

make函数在被调用后.会返回一个已被初始化的通道作为结果.这样的赋值语句使变量strChan成为了一个双向通道.该通道的元素类型为string 容量为3.

如果从通道中接收元素值.

elem:=<-strChan

其中的接收操作符<-直截了当.这条语句的含义.把strChan中的一个元素值赋给变量elem.此时进行这类操作会使当前goroutine被阻塞在这里.现在通道strChan中还没有元素值.当前goroutine被迫进入Gwaiting状态.直到strChan中有新的元素值可取时才会被唤醒.

elem,ok:=<strChan

变量ok是一个布尔类型的值.当接收操作因通道关闭而结束.该值会为false(代表操作失败).否则为true.

注:试图从一个未初始化的通道值(值为nil的通道)那里接收元素值.会造成当前goroutine的永久阻塞.

Happens before:

1).发送操作会使通道复制被发送的元素.若因通道的缓冲空间已满而无法立即复制.则阻塞进行发送操作的goroutine.复制的目的有两种.当通道已空且有接收方在等待元素值时.它会是最早等待的那个接收方持有的内存地址.否则会是通道持有的缓冲中的内存地址.

2).接收操作会使通道给出一个已发给它的元素值的副本.若因通道的缓冲空间已空而无法立即给出.则阻塞进行接收操作的goroutine.一般情况下.接收方会从通道持有的缓冲中得到元素值.

3).对于同一个元素值来说.把它发送给某个通道的操作.一定会在从通道接收它的操作完成之前完成.换句话说.在通道完全复制一个元素之前.任何goroutine都不可能从它那里接收到这个元素值的副本.

发送元素值:

发送语句由表达式 接收操作符<-和代表元素值的表达式(以下简称元素表达式)组成.其中.元素表达式的结果类型必须与通道表达式的结果类型中的元素类型存在可赋予的关系.也就是说.前者的值必须可以赋给类型为后者的变量.

对接收操作符<-两边的表达式的求值会先于发送操作执行.在对这两个表达式的求值完成之前.发送操作一定会被阻塞.

示例:

func main() {
    var strChan = make(chan string, 3)

    syncChan1 := make(chan struct{}, 1)
    syncChan2 := make(chan struct{}, 2)

    //用于接收.
    go func() {
       <-syncChan1
       fmt.Println("Received a sync signal and wait a second...[receiver] ")
       time.Sleep(time.Second)
       for {
          if elem, ok := <-strChan; ok {
             fmt.Println(elem)
          } else {
             break
          }
       }
       fmt.Println("stopped.[receiver]")
       syncChan2 <- struct{}{}
    }()

    //用于演示发送操作
    go func() {
       for _, elem := range []string{"a", "b", "c", "d", "e"} {
          strChan <- elem
          fmt.Println("Sent:", elem, "[sender]")
          if elem == "c" {
             syncChan1 <- struct{}{}
             fmt.Println("Sent a sync signal.[sender]")
          }
       }
       fmt.Println("wait 2 seconds...[sender]")
       time.Sleep(time.Second * 2)
       close(strChan)
       syncChan2 <- struct{}{}
    }()
    <-syncChan2
    <-syncChan2
}

执行结果:

流程图:

示例:

func main() {
    var mapChan = make(chan map[string]int, 3)

    syncChan := make(chan struct{}, 2)

    //用于接收.
    go func() {
       fmt.Println("Received a sync signal and wait a second...[receiver] ")
       time.Sleep(time.Second)
       for {
          if elem, ok := <-mapChan; ok {
             elem["count"]++
          } else {
             break
          }
       }
       fmt.Println("stopped.[receiver]")
       syncChan <- struct{}{}
    }()

    //用于演示发送操作
    go func() {
       countMap := make(map[string]int)
       for i := 0; i < 5; i++ {
          mapChan <- countMap
          time.Sleep(time.Second * 2)
          fmt.Printf("the count map %v. [sender]\n", countMap)
       }
       close(mapChan)
       syncChan <- struct{}{}
    }()
    <-syncChan
    <-syncChan
}

执行结果:

mapChan的元素类型属于引用类型.因此.接收方对元素值的副本的修改会影响到发送方持有的源值.

示例:

func main() {
    var mapChan = make(chan map[string]Counter, 1)

    syncChan := make(chan struct{}, 2)

    //用于接收.
    go func() {
       fmt.Println("Received a sync signal and wait a second...[receiver] ")
       time.Sleep(time.Second)
       for {
          if elem, ok := <-mapChan; ok {
             counter := elem["count"]
             counter.Count++
          } else {
             break
          }
       }
       fmt.Println("stopped.[receiver]")
       syncChan <- struct{}{}
    }()

    //用于演示发送操作
    go func() {
       countMap := map[string]Counter{
          "count": Counter{},
       }
       for i := 0; i < 5; i++ {
          mapChan <- countMap
          time.Sleep(time.Second * 2)
          fmt.Printf("the count map %v. [sender]\n", countMap)
       }
       close(mapChan)
       syncChan <- struct{}{}
    }()
    <-syncChan
    <-syncChan
}

执行结果:

把map的类型换为指针.

示例:

func main() {
    var mapChan = make(chan map[string]*Counter, 1)

    syncChan := make(chan struct{}, 2)

    //用于接收.
    go func() {
       fmt.Println("Received a sync signal and wait a second...[receiver] ")
       time.Sleep(time.Second)
       for {
          if elem, ok := <-mapChan; ok {
             counter := elem["count"]
             counter.Count++
          } else {
             break
          }
       }
       fmt.Println("stopped.[receiver]")
       syncChan <- struct{}{}
    }()

    //用于演示发送操作
    go func() {
       countMap := map[string]*Counter{
          "count": &Counter{},
       }
       for i := 0; i < 5; i++ {
          mapChan <- countMap
          time.Sleep(time.Second * 2)
          fmt.Printf("the count map %v. [sender]\n", countMap)
       }
       close(mapChan)
       syncChan <- struct{}{}
    }()
    <-syncChan
    <-syncChan
}

执行结果:

关闭通道:

调用close函数就可以关闭一个通道.这样做的时候要注意.试图向一个已关闭的通道发送元素值.会让发送操作引发运行时恐慌.应该在保证安全的前提下关闭通道.

示例:

func main() {
    dataChan := make(chan int, 5)
    syncChan1 := make(chan struct{}, 1)
    syncChan2 := make(chan struct{}, 2)
    //用于接收操作.
    go func() {
       <-syncChan1
       for {
          if elem, ok := <-dataChan; ok {
             fmt.Printf("Received: %d [receiver]\n", elem)
          } else {
             break
          }
       }
       fmt.Println("Done. [receiver]")
       syncChan2 <- struct{}{}
    }()

    go func() {
       //用于发送操作.
       for i := 0; i < 5; i++ {
          dataChan <- i
          fmt.Printf("Sent: %d [sender]\n", i)
       }
       close(dataChan)
       syncChan1 <- struct{}{}
       fmt.Println("Done. [sender]")
       syncChan2 <- struct{}{}
    }()
    <-syncChan2
    <-syncChan2
}

执行结果:

注意:

1).对于同一个通道仅允许关闭一次.对通道的重复关闭会引起运行时恐慌.

2).在调用close函数时.你需要把代表欲关闭的那个通道的变量作为参数传入.如果此时变量为nil.就会引发运行是恐慌.

长度与容量:

内建函数len和cap也是可以作用于在通道之上的.它们的作用分别是获取通道中当前元素值数量(即长度)和通道可容纳元素值的最大容量(即容量).通道的容量是在初始化时已经确定的.并且之后不能改变.而通道的长度则会随着实际情况改变.

可以通过容量来判断通道是否有缓冲.若容量为0.那么就肯定是一个非缓冲通道.否则就是一个缓冲通道.

单向channel:

单向通道可分为发送通道和接收通道.需要注意的是.无论哪一种单向通道.都不应该出现在变量的声明中.如果声明了这样一个变量:

var uselessChan chan<- int = make(chan<- int,10)

显然一个只进不出的通道没有任何意义.

所以单向通道应由双向通道变换而来.可以用这种变换来约束程序对通道的使用方式.例如.os/signal.Notify函数的声明:

func Notify(c chan<- os.Signal, sig ...os.Signal) {
   
    }

该函数第一个参数类型是发送通道类型.从表面上看.调用它的程序需要传入一个只能发送而不能接收的通道.然而不应该如此.在调用该函数的时候.应该传入一个双向通道.Go会依据该参数的声明.自动把它转换为单向通道.Notify函数中的代码只能向通道c发送元素.而不能从那里接收元素.这是一个强约束.在该函数中从通道中接收元素会造成编译错误.在函数外不受约束.

type SignalNotify interface {
    Notify(sig ...os.Signal) <-chan os.Signal
}

结果声明为一个接收通道.而非发送通道.此方法声明的约束目标是调用方.而不是实现方.Notify方法只能从结果中读取值.而不能向其发送值.

这两个方法声明的真正不同点在于使用单向通道的方式.它们分别对单向通道一端进行了约束.使得它们运用在不同的场景.

示例:

func main() {
    var strChan = make(chan string, 3)
    syncChan1 := make(chan struct{}, 1)
    syncChan2 := make(chan struct{}, 2)

    go receive(strChan, syncChan1, syncChan2)
    go send(strChan, syncChan1, syncChan2)

    <-syncChan2
    <-syncChan2
}

func send(strChan chan<- string, syncChan1 chan<- struct{}, syncChan2 chan<- struct{}) {
    for _, elem := range []string{"a", "b", "c", "d", "e"} {
       strChan <- elem
       fmt.Println("send", elem, "[sender]")
       if elem == "c" {
          syncChan1 <- struct{}{}
          fmt.Println("send a sync signal. [sender]")
       }
    }
    fmt.Println("wait 2 seconds... [sender]")
    time.Sleep(time.Second * 2)
    close(strChan)
    syncChan2 <- struct{}{}
}

func receive(strChan <-chan string, syncChan1 <-chan struct{}, syncChan2 chan<- struct{}) {
    <-syncChan1
    fmt.Println("Received a sync signal and wait a second...  [receiver]")
    time.Sleep(time.Second)
    for {
       if elem, ok := <-strChan; ok {
          fmt.Println("Received:", elem, "[Received]")
       } else {
          break
       }
    }
    fmt.Println("Stopped. [receiver]")
    syncChan2 <- struct{}{}
}

结果:

示例:

func main() {
    var ok bool
    ch := make(chan int, 1)
    _, ok = interface{}(ch).(<-chan int)
    fmt.Println("chan int => <-chan int:", ok)
    _, ok = interface{}(ch).(chan<- int)
    fmt.Println("chan int => chan<- int:", ok)

    sch := make(<-chan int, 1)
    _, ok = interface{}(sch).(chan int)
    fmt.Println("<-chan int => chan int:", ok)

    rch := make(chan<- int, 1)
    _, ok = interface{}(rch).(chan int)
    fmt.Println("chan<- int => chan int:", ok)
}

执行结果:

for语句与channel:

从一个还未初始化的通道中接收元素值会导致当前goroutine的永久阻塞.使用for循环也不例外.

for语句会不断的尝试从通道中接收元素,直到该通道关闭,在通道关闭时.如果通道中已无元素值.这条for语句的执行会立即结束.此时通道中还有遗留的元素值.for循环仍可以继续把它们取完.

示例:

func main() {
    var strChan = make(chan string, 3)
    syncChan1 := make(chan struct{}, 1)
    syncChan2 := make(chan struct{}, 2)
    go receive(strChan, syncChan1, syncChan2)
    go send(strChan, syncChan1, syncChan2)

}

func send(strChan chan<- string, syncChan1 chan<- struct{}, syncChan2 chan<- struct{}) {
    for _, elem := range []string{"a", "b", "c", "d", "e"} {
       strChan <- elem
       fmt.Println("send", elem, "[sender]")
       if elem == "c" {
          syncChan1 <- struct{}{}
          fmt.Println("send a sync signal. [sender]")
       }
    }
    fmt.Println("wait 2 seconds... [sender]")
    time.Sleep(time.Second * 2)
    close(strChan)
    syncChan2 <- struct{}{}
}

func receive(strChan <-chan string, syncChan1 <-chan struct{}, syncChan2 chan<- struct{}) {
    <-syncChan1
    fmt.Println("Received a sync signal and wait a second...  [receiver]")
    time.Sleep(time.Second)
    for elem := range strChan {
       fmt.Println("Received:", elem, "[Received]")
    }
    fmt.Println("Stopped. [receiver]")
    syncChan2 <- struct{}{}
}

select语句:

select语句是一种仅能用于通道发送和接收操作的专用语句.一条select语句执行时.会选择其中一个分支执行.在表现形式上.select语句和switch语句非常类似.但是它们选择的分支的方法完全相同.

分支选择规则:

开始执行select语句的时候.所有跟在cae关键字右边的发送语句或接收语句中的通道表达式和元素表达式都会先求值(求值顺序从左到右 自上而下).无论它们所在的case是否有可能被选择都会是这样.

示例:

var intChan1 chan int
var intChan2 chan int
var channels = []chan int{intChan1, intChan2}
var numbers = []int{1, 2, 3, 4, 5}

func main() {
    select {
    case getChan(0) <- getNumber(0):
       fmt.Println("1th case is selected.")
    case getChan(1) <- getNumber(1):
       fmt.Println("2th case is selected.")
    default:
       fmt.Println("default")
    }
}

func getNumber(i int) int {
    fmt.Printf("numbers[%d]\n", i)
    return numbers[i]
}

func getChan(i int) chan int {
    fmt.Printf("channels[%d]\n", i)
    return channels[i]
}

执行结果:

当有一个case被选中时.运行时系统就会执行该case及其包含的语句.而其他的case会被忽略.如果有多个case满足条件.那么运行时系统就会通过一个伪随机算法选中一个case.

示例:

func main() {
    chanCap := 5
    intChan := make(chan int, chanCap)
    for i := 0; i < chanCap; i++ {
       select {
       case intChan <- 1:
       case intChan <- 2:
       case intChan <- 3:
       }
    }
    for i := 0; i < chanCap; i++ {
       fmt.Printf("%d\n", <-intChan)
    }
}

执行结果:

可以尝试多运行几次对比结果.

与for语句的连用:

func main() {
    intChan := make(chan int, 10)
    for i := 0; i < 10; i++ {
       intChan <- i
    }
    close(intChan)
    syncChan := make(chan struct{}, 1)
    go func() {
    Loop:
       for {
          select {
          case i, ok := <-intChan:
             if !ok {
                fmt.Println("End")
                break Loop
             }
             fmt.Printf("Received: %v\n ", i)
          }
       }
       syncChan <- struct{}{}
    }()
    <-syncChan
}

执行结果:

非缓冲的channel:

如果在初始化一个通道时将其容量设置为0.或者直接忽略对容量的设置.就会使该通道成为一个非缓冲通道.与以异步的方式传递元素值缓冲通道不同.非缓冲通道只能同步的传递元素值.

happens before:

与缓冲通道相比.针对非缓冲通道的happen before原则有两个特别之处.

1).向此类通道发送元素值的操作会被阻塞.直到至少有一个针对该通道的接收操作进行位置.该接收操作会先得到元素值的副本.然后在唤醒发送方所在goroutine之后返回.也就是说.这时接收操作会在对应的发送操作完成之前完成.

2).从此类通道接收元素值的操作会被阻塞.直到至少有一个针对该通道的发送操作进行为止.该发送操作会直接把元素复制给接收方.然后在唤醒接收方所在的goroutine返回.也就是说.这时的发送操作会在对应的接收操作之前完成.

同步的特性:

func main() {
    sendingInterval := time.Second
    receptionInterval := time.Second * 2
    intChan := make(chan int, 0)
    go func() {
       var ts0, ts1 int64
       for i := 1; i <= 5; i++ {
          intChan <- i
          ts1 = time.Now().Unix()
          if ts0 == 0 {
             fmt.Println("Sent", i)
          } else {
             fmt.Printf("Sent %d [interval: %d s]\n", i, ts1-ts0)
          }
          ts0 = time.Now().Unix()
          time.Sleep(sendingInterval)
       }
       close(intChan)
    }()
    var ts0, ts1 int64
Loop:
    for {
       select {
       case i, ok := <-intChan:
          if !ok {
             break Loop
          }
          ts1 = time.Now().Unix()
          if ts0 == 0 {
             fmt.Println("Received", i)
          } else {
             fmt.Printf("Received %d [interval: %d s]\n", i, ts1-ts0)
          }
       }
       ts0 = time.Now().Unix()
       time.Sleep(receptionInterval)
    }
    fmt.Println("\nEnd...")
}

执行结果:

time包与channel:

定时器:

func main() {
    timer := time.NewTimer(2 * time.Second)
    fmt.Printf("Present time:%v.\n", time.Now())
    expirationTime := <-timer.C
    fmt.Printf("expiration Time:%v.\n", expirationTime)
    fmt.Printf("stop time:%v.\n", timer.Stop())
}

执行结果:

程序中<-time.C会一直阻塞.直到定时器到期.停止定时器的结果是false.因为定时器那时已经过期了.

func main() {
    intChan := make(chan int, 1)
    go func() {
       time.Sleep(1 * time.Second)
       intChan <- 1
    }()

    select {
    case e := <-intChan:
       fmt.Printf("Received: %d\n", e)
    case <-time.NewTimer(time.Millisecond * 500).C:
       fmt.Println("Timeout")
    }
}

执行结果:

第二个case语句初始化了一个500ms的定时器.并试图立即从它的字段C中接收元素值.一旦定时器到期就会完成接收操作.select语句也就执行结束了.

func main() {
    intChan := make(chan int, 1)
    go func() {
       for i := 0; i < 5; i++ {
          time.Sleep(time.Second)
          intChan <- i
       }
       close(intChan)
    }()
    timeout := time.Millisecond * 500
    var timer *time.Timer
    for {
       if timer == nil {
          timer = time.NewTimer(timeout)
       } else {
          timer.Reset(timeout)
       }
       select {
       case i, ok := <-intChan:
          if !ok {
             fmt.Println("end")
             return
          }
          fmt.Printf("Received: %d\n", i)
       case <-timer.C:
          fmt.Println("timeout")
       }
    }
}

执行结果:

断续器:

func main() {
    intChan := make(chan int, 1)
    ticker := time.NewTicker(time.Second)
    go func() {
       for _ = range ticker.C {
          select {
          case intChan <- 1:
          case intChan <- 2:
          case intChan <- 3:
          }
       }
       fmt.Println("End.[sender]")
    }()
    var sum int
    for e := range intChan {
       fmt.Printf("Received: %v\n", e)
       sum += e
       if sum > 10 {
          fmt.Printf("Got: %v\n", sum)
          break
       }
    }
    fmt.Println("End.[receiver]")
}

执行结果:

语雀地址www.yuque.com/itbosunmian…?

《Go.》 密码:xbkk 欢迎大家访问.提意见.

土花曾染湘娥黛.铅泪难消.清韵谁敲.不是犀椎是凤翘. 只应长伴端溪紫.割取秋潮.鹦鹉偷教.方响前头见玉箫. 纳兰

如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路