深入理解 Golang Channel 并发编程

一、基础简介

Golang使用Groutine和channels实现了CSP(Communicating Sequential Processes)模型,channles在goroutine的通信和同步中承担着重要的角色。

这种方式的优点是通过提供原子的通信原语,避免了竞态情形(race condition)下复杂的锁机制。
channel 可以看成一个 FIFO 队列,对 FIFO 队列的读写都是原子的操作,不需要加锁。

Golang 语言中Channel类型的定义格式如下:


chan Tr          // 可以接收和发送类型为 Tr 的数据
chan <- float64  // 只可以用来发送 float64 类型的数据
<- chan int      // 只可以用来接收 int 类型的数据

可选的<-代表channel的方向。如果没有指定方向,那么Channel就是双向的,既可以接收数据,也可以发送数据。
默认情况下,channel发送方和接收方会一直阻塞直到对方准备好发送或者接收,这就使得Go语言无需加锁或者其他条件,天然支持了并发。
例:

c := make(chan bool) 	//创建一个无缓冲的bool型Channel
c <- x        			//向一个Channel发送一个值
<- c          			//从一个Channel中接收一个值
x = <- c      			//从Channel c接收一个值并将其存储到x中
x, ok = <- c  			//从Channel接收一个值,如果channel关闭了或没有数据,那么ok将被置为false

使用 make 初始化Channel,还可以设置容量:

make(chan int, 100)      //创建一个有缓冲的int型Channel

Channel缓冲介绍
容量(capacity)代表Channel容纳的最多的元素的数量,代表Channel的缓冲的大小。 如果没有设置容量,或者容量设置为0, 说明Channel没有缓冲。
在实际开发中,你可以在多个goroutine从/往一个channel 中 receive/send 数据, 不必考虑额外的同步措施。
Channel可以作为一个先入先出(FIFO)的队列,接收的数据和发送的数据的顺序是一致的,不带缓冲的Channel兼具通信和同步两种特性。

二、channel 应用实例

实例1代码如下

package main

 import "fmt"

 func main() {
         ch1 := make(chan int,2)
         //send
         go func() {
                 for i := 0; i < 8; i++ {
                         fmt.Printf("Sender :sending element %v ..\n",i)
                         ch1 <- i
                 }

                 fmt.Println("Sender : close the channel ...")
                 close(ch1)
         }()

         //receiver
         for {
                 elem, ok := <- ch1
                 if !ok {
                         fmt.Println("Receiver: closed channel..")
                         break
                 }
                 fmt.Printf("Receiver: received an element: %v \n",elem)
         }

         fmt.Println("main end..")
 }

上面示例的程序中,我们创建了一个带缓冲的INT类型Channel,然后在一个goroutine 循环向 channel 中发送序列数据,然后在一个goroutine用 for循环方式读取 <-ch1 的内容。
在这里我们利用Channel 实现了线程间(准确的说,goroutine之间的)通信,因为channel发送方和接收方会一直阻塞直到对方准备好发送或者接收,这就使得我们在程序等待 ch1关闭而无需其他的同步操作。

  • 缓存ch1 := make(chan int,0) 或 ch1 := make(chan int); 运行结果如下:
~/gomod/src/channel$ go run channel.go 
Sender :sending element 0 ..
Sender :sending element 1 ..
Receiver: received an element: 0 
Receiver: received an element: 1 
Sender :sending element 2 ..
Sender :sending element 3 ..
Receiver: received an element: 2 
Receiver: received an element: 3 
Sender :sending element 4 ..
Sender :sending element 5 ..
Receiver: received an element: 4 
Receiver: received an element: 5 
Sender :sending element 6 ..
Sender :sending element 7 ..
Receiver: received an element: 6 
Receiver: received an element: 7 
Sender : close the channel ...
Receiver: closed channel..
main end..
  • 缓存ch1 := make(chan int,5); 运行结果如下:
robot@ubuntu:~/gomod/src/channel$ go run channel.go 
Sender :sending element 0 ..
Sender :sending element 1 ..
Sender :sending element 2 ..
Sender :sending element 3 ..
Sender :sending element 4 ..
Sender :sending element 5 ..
Sender :sending element 6 ..
Receiver: received an element: 0 
Receiver: received an element: 1 
Receiver: received an element: 2 
Receiver: received an element: 3 
Receiver: received an element: 4 
Receiver: received an element: 5 
Receiver: received an element: 6 
Sender :sending element 7 ..
Sender : close the channel ...
Receiver: received an element: 7 
Receiver: closed channel..
main end..
  • 缓存ch1 := make(chan int,10); 运行结果如下:
robot@ubuntu:~/gomod/src/channel$ go run channel.go 
Sender :sending element 0 ..
Sender :sending element 1 ..
Sender :sending element 2 ..
Sender :sending element 3 ..
Sender :sending element 4 ..
Sender :sending element 5 ..
Sender :sending element 6 ..
Sender :sending element 7 ..
Sender : close the channel ...
Receiver: received an element: 0 
Receiver: received an element: 1 
Receiver: received an element: 2 
Receiver: received an element: 3 
Receiver: received an element: 4 
Receiver: received an element: 5 
Receiver: received an element: 6 
Receiver: received an element: 7 
Receiver: closed channel..
main end..

实例2 代码如下:

package main

import (
        "fmt"
        "time"
)

func main() {
        N := 10
        exit := make (chan struct{})
        done := make(chan struct{},N)

        for i :=0; i< N; i++ {
                go func(n int){
                        for {
                                select {
                                case <- exit:
                                        fmt.Printf("worker goroutine # %d exit \n\r ",n)
                                        done <- struct{}{}
                                        return
                                case <- time.After(time.Second):
                                        fmt.Printf("worker goroutine # %d is working ..\n", n)
                                }
                        }
                }(i)
        }

        time.Sleep(3*time.Second)
        fmt.Printf("sender exit channel... \n\r")
        close(exit)  //关闭 channel 通知协程退出线程

        for i := 0; i < N;i++ {
                <- done
        }

        fmt.Printf("main goroutine exit..\n")
}

运行效果如下:

robot@ubuntu:~/gomod/src/channel$ go run channel1.go 
worker goroutine # 8 is working ..
worker goroutine # 5 is working ..
worker goroutine # 0 is working ..
worker goroutine # 2 is working ..
worker goroutine # 9 is working ..
worker goroutine # 4 is working ..
worker goroutine # 6 is working ..
worker goroutine # 7 is working ..
worker goroutine # 3 is working ..
worker goroutine # 1 is working ..
worker goroutine # 8 is working ..
worker goroutine # 9 is working ..
worker goroutine # 5 is working ..
worker goroutine # 0 is working ..
worker goroutine # 2 is working ..
worker goroutine # 6 is working ..
worker goroutine # 4 is working ..
worker goroutine # 7 is working ..
worker goroutine # 3 is working ..
worker goroutine # 1 is working ..
sender exit channel... 
worker goroutine # 8 exit 
 worker goroutine # 2 exit 
 worker goroutine # 1 exit 
 worker goroutine # 0 exit 
 worker goroutine # 5 exit 
 worker goroutine # 9 exit 
 worker goroutine # 6 exit 
 worker goroutine # 7 exit 
 worker goroutine # 3 exit 
 worker goroutine # 4 exit 
 main goroutine exit..

关闭管道 exit 后,各 goroutine 携程检测到exit管道被关闭,自动退出协程。

上一篇:测试平台系列(82) 解决APScheduler重复执行的问题


下一篇:C# 逆变与协变