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

全部评论

相关推荐

01-14 15:08
东南大学 Java
点赞 评论 收藏
分享
评论
1
1
分享

创作者周榜

更多
牛客网
牛客企业服务