代码逻辑
使用开启十个协程处理生产数据,一个协程来处理数据。
当处理数据的协程挂掉的时候。生产的协程也需要停止生产。
主线程需要知道 消费者是正常消费还是异常退出,方便做出对应的处理。
示例代码
package main
import (
"log"
"runtime"
"runtime/debug"
"sync"
"time"
)
type TempStruct struct {
Num int
}
func main() {
var wg sync.WaitGroup
//缓冲的数量根据具体的业务来决定 这里设置十个缓冲
ch := make(chan *TempStruct, 10)
closeCh := make(chan struct{})
for i := 0; i < 10; i++ {
// 在执行之前将waitGroup add 1,而不是在协程起来的时候Add(1)
// 控制权在调用方,因为对于什么时候协程会起来 调用方是未知的
// 保证协程启动
// 如果选择在协程起来的时候Add(1)的话
// 极端情况可能会出现 并非全部的协程都运行(前面启动的协程很快就 wg.done) 然后wg.wait()直接放行 出现未知的错误
wg.Add(1)
go DoFunc(ch, &wg, closeCh)
}
var x interface{}
//此处使用闭包修改x的值 来判断 handler是否执行成功
go func(ch <-chan *TempStruct, closeCh chan<- struct{}) {
defer func() {
if x = recover(); x != nil {
log.Printf("Do func panic,x = %+v,stack = %s", x, string(debug.Stack()))
}
close(closeCh)
}()
Handler(ch)
}(ch, closeCh)
wg.Wait()
close(ch)
select {
case _, ok := <-closeCh:
if !ok {
log.Println("main goroutine get close message...")
}
}
if x != nil {
// 模拟打印错误信息...
log.Println("handle err,err = " + x.(string))
return
}
log.Println("succeed")
}
func Handler(ch <-chan *TempStruct) {
num := 0
//第一种方法 使用for 来获取消息
//for {
// time.Sleep(time.Second)
//
// ts, ok := <-ch
// if ok {
// log.Printf("get message :%+v,num = %d", ts, num)
// num++
// } else {
// log.Printf("chan is closed....")
// return
// }
//}
// 第二种方法 使用 range 获取协程中的消息 如果管道返回的是零值代表已经被关闭
for ts := range ch {
time.Sleep(time.Millisecond)
if ts != nil {
log.Printf("get message :%+v,num = %d", ts, num)
// 模拟handler执行panic 或执行错误
if num == 20 {
panic("panic.......")
}
num++
} else {
log.Printf("chan is closed....")
return
}
}
}
func DoFunc(ch chan<- *TempStruct, wg *sync.WaitGroup, closeCh <-chan struct{}) {
defer func() {
if x := recover(); x != nil {
log.Printf("Do func panic,x = %+v,stack = %s", x, string(debug.Stack()))
}
wg.Done()
}()
//Do
for i := 0; i < 10; i++ {
// 模拟写数据
// select 中可以放任何可能发生阻塞的方法
// 不止可以读 也可以写
select {
// 此处替换为 default : ch <- &TempStruct{i}: 会发生死锁 原因是管道关闭后 缓冲满后会一直阻塞
case ch <- &TempStruct{i}:
log.Println("put i =", i)
case _, ok := <-closeCh:
if !ok {
log.Println("doFunc got close message... return")
return
}
}
}
log.Println("goroutine ", runtime.NumGoroutine(), "put message over... func out.....")
}
知识点
- select 中可以放任何可能发生阻塞的方法 不止可以从管道里读 也可以往管道里写。
- 除了管道外 可以利用闭包 获取方法的执行结果。
- 缓冲的数量根据具体的业务来决定。
- 使用waitGroup调用协程时 调用方执行add 协程在defer中done掉 保证协程都能启动 协程关闭后都能够释放。
- 不仅可以在 for{}中获取管道中的数据 也可以通过range 判断是否为零值来查看管道是否关闭。