channel的应用场景总结
1、信号传递
有 4 个 goroutine,编号为 1、2、3、4。每秒钟会有一个 goroutine 打印出它自己的编号,要求你编写程序,让输出的编号总是按照 1、2、3、4、1、2、3、4……这个顺序打印出来。
type Token struct{}
func newWorker(id int, ch chan Token, nextCh chan Token) {
for {
token := <-ch // 取得令牌
fmt.Println((id + 1)) // id从1开始
time.Sleep(time.Second)
nextCh <- token
}
}
func main() {
chs := []chan Token{make(chan Token), make(chan Token), make(chan Token), make(chan Token)}
// 创建4个worker
for i := 0; i < 4; i++ {
go newWorker(i, chs[i], chs[(i+1)%4])
}
//首先把令牌交给第一个worker
chs[0] <- struct{}{}
select {}
}
2、信号通知
使用 chan 实现程序的 graceful shutdown,在退出之前执行一些连接关闭、文件 close、缓存落盘等一些动作。
func main() {
var closing = make(chan struct{})
var closed = make(chan struct{})
go func() {
// 模拟业务处理
for {
select {
case <-closing:
return
default:
// ....... 业务计算
time.Sleep(100 * time.Millisecond)
}
}
}()
// 处理CTRL+C等中断信号
termChan := make(chan os.Signal)
signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM)
<-termChan
close(closing)
// 执行退出之前的清理动作
go doCleanup(closed)
select {
case <-closed:
case <-time.After(time.Second):
fmt.Println("清理超时,不等了")
}
fmt.Println("优雅退出")
}
func doCleanup(closed chan struct{}) {
time.Sleep((time.Minute))
close(closed)
}
3、使用channel的声明控制读写权限
// 只有generator进行对outCh进行写操作,返回声明
// <-chan int,可以防止其他协程乱用此通道,造成隐藏bug
func generator(int n) <-chan int {
outCh := make(chan int)
go func(){
for i:=0;i<n;i++{
outCh<-i
}
}()
return outCh
}
// consumer只读inCh的数据,声明为<-chan int
// 可以防止它向inCh写数据
func consumer(inCh <-chan int) {
for x := range inCh {
fmt.Println(x)
}
}
4、超时控制
使用select和time.After,看操作和定时器哪个先返回,处理先完成的,就达到了超时控制的效果。
func doWithTimeOut(timeout time.Duration) (int, error) {
select {
case ret := <-do():
return ret, nil
case <-time.After(timeout):
return 0, errors.New("timeout")
}
}
func do() <-chan int {
outCh := make(chan int)
go func() {
// do work
}()
return outCh
}
5、定时任务
使用select和time.NewTicker,实现定时任务。
func main() {
timer := time.NewTicker(time.Second)
for {
select {
case <-timer.C:
fmt.Println("执行了") // 每隔1秒执行一次
}
}
}
6、控制并发数量
var limit = make(chan int, 3)
func main() {
// 通过channel控制最大并发数量
tasks := [...]int{11, 22, 33, 44, 55, 66, 77, 88, 99, 100}
for i, v := range tasks {
// 为每一个任务开启一个goroutine
go func(i, v int) {
// 通过channel控制goroutine最大并发数量
limit <- -1
fmt.Println(i, v)
time.Sleep(time.Second)
<-limit
}(i, v)
}
time.Sleep(time.Second * 4)
}
7、生产者消费者模型
服务启动时,启动n个worker,作为工作协程池,这些协程工作在一个for无限循环里, 从某个channel消费工作任务并执行。
func main() {
tasksChan := make(chan int, 100) // 任务队列
go workerTask(tasksChan) // 开启处理任务的协程池
// 发起任务
for i := 0; i < 10; i++ {
tasksChan <- i
}
select {
case <-time.After(time.Second * 3):
}
}
func workerTask(tasksChan chan int) {
// 开启5个协程去处理任务队列中的数据
GOS := 5
for i := 0; i < GOS; i++ {
// 局部变量在堆栈上存储,也是变量逃逸的一种场景(解决方法:使用闭包)
go func(i int) {
for {
value := <-tasksChan
fmt.Printf("finish task: %d by worker %d\n", value, i)
time.Sleep(time.Second)
}
}(i)
}
}
8、优雅退出
package main
import (
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
var closing = make(chan struct{})
var closed = make(chan struct{})
go func() {
for {
select {
case <-closing:
return
default:
fmt.Println("业务逻辑...")
time.Sleep(1 * time.Second)
}
}
}()
termChan := make(chan os.Signal)
// 监听退出信号
signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM)
<-termChan
// 退出中
close(closing)
// 退出之前清理一下
go doCleanup(closed)
select {
case <-closed:
case <-time.After(time.Second):
log.Println("清理超时不等了")
}
log.Println("优雅退出")
}
func doCleanup(closed chan struct{}) {
time.Sleep(time.Minute)
// 清理完后退出
close(closed)
}
参考:(36条消息) Golang并发编程-Channel的使用场景分析_pbrong的博客-CSDN博客_golang使用场景
参考:(36条消息) Go channel的使用场景,用法总结_Mark66890620的博客-CSDN博客
参考:(36条消息) go channel原理及使用场景_六月的的博客-CSDN博客

浙公网安备 33010602011771号