简析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语言学习笔记、语法知识、技术要点和个人理解及实战

