for-select
for-select 是一个很常见的用法,select 语句可以从多个可读的 channel 中随机选取一个执行。
向 channel 发送迭代变量:
ch := make(chan int, 2)
for _, s := range []int{1, 2} {
select {
case ch <- s:
}
}
循环等待停止:
// 第一种
for {
select {
case <- done:
return
default:
// 进行非抢占式任务
}
}
// 第二种
for {
select {
case <- done:
return
default:
}
// 进行非抢占式任务
}
第一种是指,当我们输入 select 语句时,如果完成的 channel 尚未关闭,我们将执行 default 语句;第二种是指,如果已经完成的 channel 未关闭,我们将退出 select 语句并继续执行 for 循环的其余部分。
超时控制
等待 3 s 后,如果 s.stopc 还没有读出数据或者被关闭,就直接结束。
select {
case <-time.After(3 * time.Second):
case <-s.stopc:
return false
}
done channel
防止 goroutine 泄露。用 done channel 在父子 goroutine 之间建立一个 “信号通道”,父 goroutine 可以将该 channel 传递给子 goroutine ,然后在想要取消子 goroutine 的时候关闭该 channel。
func main() {
doneChan := make(chan interface{})
go func(done <-chan interface{}) {
for {
select {
case <-done:
return
default:
}
}
}(doneChan)
// 父 goroutine 关闭子 goroutine
close(doneChan)
}
确保 goroutine 不泄露的方法,就是规定一个约定:如果 goroutine 负责创建 goroutine,它也负责确保它可以停止 goroutine。
定时执行某个任务
//每隔 1 秒种,执行一次定时任务。
func worker() {
ticker := time.Tick(1 * time.Second)
for {
select {
case <- ticker:
// 执行定时任务
}
}
}
解耦生产方和消费方
func main() {
taskCh := make(chan int, 10)
go worker(taskCh)
// 塞任务
for i := 0; i < 10; i++ {
taskCh <- i
}
// 等待 1 小时
select {
case <-time.After(time.Hour):
}
}
func worker(taskCh <-chan int) {
const N = 5
// 启动 5 个工作协程
for i := 0; i < N; i++ {
go func(id int) {
for {
task := <- taskCh
time.Sleep(time.Second)
}
}(i)
}
}
5 个工作协程在不断地从工作队列里取任务,生产方只管往 channel 发送任务即可,解耦生产方和消费方。
控制并发数
在并发数较高时,因为任务执行过程依赖其他模块的一些资源
对请求的速率有限制,这时就可以通过 channel 来控制并发数。
var limit = make(chan int, 3)
func main() {
// …………
for _, w := range work {
go func() {
limit <- 1
proc()
<-limit
}()
}
// …………
}
构建一个缓冲型的 channel,容量为 3。接着遍历任务列表,每个任务启动一个 goroutine 去完成。真正执行任务,访问第三方模块动作在 proc() 中完成,在执行 proc() 之前,先要从 limit 中拿“许可证”,拿到许可证之后,才能执行 proc(),并且在执行完任务,要将“许可证”归还。这样就可以控制同时运行的 goroutine 数。