简析go带缓冲channel
带缓冲channel可以通过带capacity参数的内置make函数创建:
c := make(chan T, capacity) // T为channel中元素的类型,capacity为带缓冲channel的缓冲区容量
由于带缓冲channel的运行时层实现带有缓冲区,因此对带缓冲channel的发送操作在缓冲区未满、接收操作在缓冲区非空的情况下是异步的(发送或接收无需阻塞等待)。即对于带缓冲channel,在缓冲区无数据或有数据但未满的情况下,对其进行发送操作的goroutine不会阻塞,在缓冲区已满的情况下,对其进行发送操作的goroutine会阻塞;在缓冲区为空的情况下对其进行接收操作的goroutine会阻塞。
带缓冲channel的常见用法:
1、用作消息队列:可以指定消息数量,如果消息数量大于指定数量,必须有其他线程接收消息,否则阻塞
import ( "fmt" ) func main() { c := make(chan int, 1) go func() { c <- 1 println("goroutine 1") }() go func() { c <- 2 println("goroutine 2") }() a := <-c fmt.Println(a) }
2、用作计数信号量
go并发设计的一个惯用法是将带缓冲channel用作计数信号量(counting semaphore)。带缓冲channel中的当前数据个数代表的是当前同时处于活跃状态的goroutine的数量,带缓冲channel的容量代表允许同时处于活跃状态的goroutine的最大数量。一个发往带缓冲channel的发送操作表示获取一个信号量槽位,而一个来自带缓冲channel的接收操作则表示释放一个信号量槽位。
import ( "log" "sync" "time" ) var countingSemaphore = make(chan struct{}, 3) var jobs = make(chan int) func main() { go func() { for i := 0; i < 10; i++ { jobs <- i } close(jobs) }() var wg sync.WaitGroup for i := range jobs { wg.Add(1) go func(i int) { defer wg.Done() countingSemaphore <- struct{}{} log.Printf("do job %d\n", i) time.Sleep(2 * time.Second) <-countingSemaphore }(i) } wg.Wait() }
上例中jobs为一个缓冲区容量为3的带缓冲channel,同一时间最多允许3个goroutine处于活动状态。countingSemaphore为计数信号量,同jobs,即同一时间最多允许3个goroutine处于活动状态。运行代码结果:
$ go run go-channel-case.go 2023/05/14 23:55:30 do job 1 2023/05/14 23:55:30 do job 0 2023/05/14 23:55:30 do job 3 2023/05/14 23:55:32 do job 2 2023/05/14 23:55:32 do job 4 2023/05/14 23:55:32 do job 5 2023/05/14 23:55:34 do job 7 2023/05/14 23:55:34 do job 6 2023/05/14 23:55:34 do job 9 2023/05/14 23:55:36 do job 8
从代码运行结果可以看出,同一时间处理job的goroutine数量最多为3个。
Go语言基础及实战 文章被收录于专栏
Go语言学习笔记、语法知识、技术要点和个人理解及实战