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博客