Golang 退出 goroutine的几种方式

传统方式

在刚开始学go的时候,没用过Context包,那么退出携程的方式一般有这么几种

使用携 chan 发送消息通知,这种一般只适合单个goroutine

func exit01() {
	done := make(chan bool)
	go func() {
		for {
			select {
			case <-done:
				fmt.Println("退出携程")
				return
			default:
				fmt.Println("监控中...")
				time.Sleep(1 * time.Second)
			}
		}
	}()
	time.Sleep(3 * time.Second)
	done <- true
	time.Sleep(5 * time.Second)
	fmt.Println("程序退出")
}

使用关闭 chan 的方式通知多个goroutine退出

func exit02() {
	done :=make(chan bool)
	go func() {
		for{
			select {
			case <-done:
				fmt.Println("退出携程01")
				return
			default:
				fmt.Println("监控01...")
				time.Sleep(1*time.Second)
			}
		}
	}()

	go func() {
		for res :=range done{ //没有消息阻塞状态,chan关闭 for 循环结束
			fmt.Println(res)
		}
		fmt.Println("退出监控03")
	}()

	go func() {
		for{
			select {
			case <-done:
				fmt.Println("退出携程02")
				return
			default:
				fmt.Println("监控02...")
				time.Sleep(1*time.Second)
			}
		}
	}()
	time.Sleep(3*time.Second)
	close(done)
	time.Sleep(5*time.Second)
	fmt.Println("退出程序")
}

初识 Context包

一个用于手动控制 goroutine 退出或者结束

获取 context上下文两种方式

ctx := context.Background() //这只能用于高等级(在 main 或顶级请求处理中)。这能用于派生我们稍后谈及的其他 context

ctx := context.TODO()  // 也只能用于高等级或当您不确定使用什么 context,或函数以后会更新以便接收一个 context 

他们的底层实现完全一致,不同的是,静态分析工具可以使用它来验证 context 是否正确传递,
这是一个重要的细节,因为静态分析工具可以帮助在早期发现潜在的错误,并且可以连接到 CI/CD 管道

var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)

使用context.WithTimeout,主动调用 cancel()方法,可以在时间超时之前退出 goroutine

func exit03() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	go func() {
		for{
			select {
			case <-ctx.Done():
				fmt.Println("退出携程")
				return
			default:
				fmt.Println("请求中..")
				time.Sleep(1*time.Second)
			}
		}
	}()
	time.Sleep(5*time.Second)
	//cancel() //也可以手动调用 cancel()方法退出
	//time.Sleep(2*time.Second)
	fmt.Println("程序退出")

}

使用 context.WithCanel()方法,根据外部条件手动调用 cancel()方法退出
只有创建它的函数才能调用取消函数来取消此 context。如果您愿意,可以传递取消函数,但是,强烈建议不要这样做。
这可能导致取消函数的调用者没有意识到取消 context 的下游影响。可能存在源自此的其他 context,
这可能导致程序以意外的方式运行。简而言之,永远不要传递取消函数

func exit04() {
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		for{
			select {
			case <-ctx.Done():
				fmt.Println("退出携程")
				return
			default:
				fmt.Println("监控01")
				time.Sleep(1*time.Second)
			}
		}
	}()

	time.Sleep(5*time.Second)
	cancel()
	time.Sleep(2*time.Second)
	fmt.Println("退出程序")

}   

使用 context.WithDeadLine() ,在指定的时间退出 goroutine

func exit05() {
	stringTime := "2019-08-11 09:08:01"
	loc, _ := time.LoadLocation("Local")
	the_time, _ := time.ParseInLocation("2006-01-02 15:04:05", stringTime, loc)
	ctx, _ := context.WithDeadline(context.Background(), the_time)
	go func() {
		for{
			select {
			case <-ctx.Done():
				fmt.Println("退出 goroutine")
				return
			default:
				fmt.Println("监控...")
				time.Sleep(1*time.Second)
			}
		}
	}()

	time.Sleep(60*time.Second)
	fmt.Println("程序退出")

}

使用context.WithValue()传值,在所有的context树中都能获取到该值,如果设置相同的key 则覆盖该值
不建议使用 context 值传递关键参数,而是函数应接收签名中的那些值,使其显式化。

func exit06() {
	ctx := context.WithValue(context.Background(), "msg", "hello word")
	go func(ctx context.Context) {
		fmt.Println(ctx.Value("msg"))
	}(ctx)
	time.Sleep(2*time.Second)
	fmt.Println("程序退出")
}

相关建议约束

  • context.Background 只应用在最高等级,作为所有派生 context 的根。
  • context.TODO 应用在不确定要使用什么的地方,或者当前函数以后会更新以便使用 context。
  • context 取消是建议性的,这些函数可能需要一些时间来清理和退出。
  • context.Value 应该很少使用,它不应该被用来传递可选参数。这使得 API 隐式的并且可以引起错误。取而代之的是,这些值应该作为参数传递。
  • 不要将 context 存储在结构中,在函数中显式传递它们,最好是作为第一个参数。
  • 永远不要传递不存在的 context 。相反,如果您不确定使用什么,使用一个 ToDo context。
  • Context 结构没有取消方法,因为只有派生 context 的函数才应该取消 context。

time包中的Ticker,Timer

ticker是每隔一段时间就会触发依次

timer是定时器,只会触发一次

func test01() {

//每隔一段时间触发一次
ticker := time.NewTicker(time.Second * 1)
go func() {
	for{
		<-ticker.C
		fmt.Println("ticker")
	}
}()

//只会触发一次
timer := time.NewTimer(time.Second * 2)
go func() {
	for{
		<-timer.C
		fmt.Println("timer")
	}
}()
time.Sleep(time.Second * 20)
}

time.NewTimer和Reset()函数实现定时触发,Reset()函数可能失败,经测试。

参考:https://studygolang.com/articles/13866?fr=sidebar

posted @ 2019-08-11 09:50  NiRao  阅读(6050)  评论(0编辑  收藏  举报