golang中通知协程退出的方式

1 需求分析

go语言中通知子 goroutine 退出的三种方式

  • 方式1 通过全局变量:如果全局变量为真就退出
  • 方式2 通过通道:协程在通道里面取到true就退出
  • 方式3 通过context:通过调用ctx.Done()方法通知所有的协程退出
  • context.WithTimeout超时退出

2 通过全局变量和通道方式

全局变量方式存在的问题:

  1. 使用全局变量在跨包调用时不容易统一
  2. 如果worker中再启动goroutine,就不太好控制了。

管道方式存在的问题:

使用全局变量在跨包调用时不容易实现规范和统一,需要维护一个共用的channel

package main

import (
	"fmt"
	"sync"
	"time"
)

//全局变量
var stopLabel bool

//全局通道
var stopChannel chan bool

var wg sync.WaitGroup

func test01() {
	defer wg.Done()
	for {
		fmt.Println("test01")
		time.Sleep(time.Millisecond * 10)

		//如果全局变量stopLabel为真就退出
		if stopLabel {
			break
		}
	}
}

func test02() {
	defer wg.Done()

	//通过loop的标志位终止
loop:
	for {
		fmt.Println("test02")
		time.Sleep(time.Millisecond * 30)

		/*
			监听的case中,没有满足条件的就阻塞
			多个满足条件的就任选一个执行
			select本身不带循环,需要外层的for
			default通常不用,会产生忙轮询
			break只能跳出select中的一个case
		*/
		select {
		case <-stopChannel:
			break loop
		default:
		}
	}
	//不可下面这种写法,chan是阻塞的
	// for {
	// 	fmt.Println("test02")
	// 	time.Sleep(time.Millisecond * 30)

	// 	if <-stopChannel {
	// 		fmt.Println("test02退出")
	// 		break
	// 	}
	// }
}

//方式1 通过全局变量
func globalExit() {
	wg.Add(1)
	go test01()

	//5s以后就停止
	time.Sleep(time.Second * 5)
	stopLabel = true
	wg.Wait()
}

//方式2 通过通道
func channelExit() {
	//初始化通道
	stopChannel = make(chan bool, 1)
	wg.Add(1)
	go test02()

	//5s以后就停止
	time.Sleep(time.Second * 5)
	stopChannel <- true
	wg.Wait()
}

func main() {
	//go语言中通知子 goroutine 退出的三种方式

	//方式1 通过全局变量
	globalExit()
	fmt.Println("方式1 通过全局变量 的方法结束")

	//方式2 通过通道
	channelExit()

	//方式3 通过context
}

3 方式3 通过context

context的优势在于:

当子goroutine又开启另外一个goroutine时,只需要将ctx传入即可

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

//第2个协程
func test04(ctx context.Context) {
	defer wg.Done()
loop:
	for {
		fmt.Println("test04")
		time.Sleep(time.Millisecond * 10)

		// 等待上级通知
		select {
		case <-ctx.Done():
			break loop
		default:
		}
	}

}

//第一个协程
func test03(ctx context.Context) {
	defer wg.Done()
	//第一个协程调用第2个协程 两个协程都会收到ctx的信号而终止
	go test04(ctx)

loop:
	for {
		fmt.Println("test03")
		time.Sleep(time.Millisecond * 10)

		// 等待上级通知
		select {
		case <-ctx.Done():
			break loop
		default:
		}
	}

}

//方式3 通过context
func contextExit() {
	wg.Add(2) //2个协程在跑
	ctx, cancel := context.WithCancel(context.Background())
	go test03(ctx)

	//5s以后就停止
	time.Sleep(time.Second * 3)
	cancel() // 通知子goroutine结束

	wg.Wait()
}

func main() {
	//go语言中通知子 goroutine 退出的三种方式
	//方式3 通过context
	contextExit()
	fmt.Println("方式3 通过context 结束")
}

4 context.WithTimeout超时退出

取消此上下文将释放与其相关的资源,因此代码应该在此上下文中运行的操作完成后立即调用cancel,通常用于数据库或者网络连接的超时控制。具体示例如下:

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func test02(ctx context.Context) {
	defer wg.Done()

	//通过loop的标志位终止
loop:
	for {
		//处理的事务
		fmt.Println("test02")
		time.Sleep(time.Millisecond * 30)

		/*
			监听的case中,没有满足条件的就阻塞
			多个满足条件的就任选一个执行
			select本身不带循环,需要外层的for
			default通常不用,会产生忙轮询
			break只能跳出select中的一个case
		*/
		select {
		case <-ctx.Done():
			break loop
		default:
		}
	}

}

//超时退出
func timeoutExit() {
	//3s就超时
	timeLimit := time.Second * 3
	ctx, cancel := context.WithTimeout(context.Background(), timeLimit)
	wg.Add(1)
	go test02(ctx)

	//5s以后再回收相关联的资源
	time.Sleep(time.Second * 5)
	cancel()
	wg.Wait()
}

func main() {

	//超时退出
	timeoutExit()
	fmt.Println("超时退出 的方法结束")

}

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐维康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值