一个完整的协程示例 让你了解 协程 管道 select的使用方法

代码逻辑

使用开启十个协程处理生产数据,一个协程来处理数据。

当处理数据的协程挂掉的时候。生产的协程也需要停止生产。

主线程需要知道 消费者是正常消费还是异常退出,方便做出对应的处理。

示例代码

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.....")
}

知识点

  1. select 中可以放任何可能发生阻塞的方法 不止可以从管道里读 也可以往管道里写。
  2. 除了管道外 可以利用闭包 获取方法的执行结果。
  3. 缓冲的数量根据具体的业务来决定。
  4. 使用waitGroup调用协程时 调用方执行add 协程在defer中done掉 保证协程都能启动 协程关闭后都能够释放。
  5. 不仅可以在 for{}中获取管道中的数据 也可以通过range 判断是否为零值来查看管道是否关闭。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值