【GoLang】关于协程的关闭

协程在正常情况下的关闭机制

对于一个正在运行的协程,正常在以下2种情况下会关闭。

  1. 协程运行完了,会自动关闭。
  2. 协程种发生panic时,会自动关闭。此时整个程序也会停止运行。在这里插入图片描述

为什么需要关闭协程

对于一个正在运行的协程,其还未达到终止条件,但是其又没有再继续执行下去的必要,此时就需要主动关闭其执行,从而保证程序的健壮性和性能。

如何优雅的关闭协程

优雅得关闭goroutine的执行,我们可以遵循以下三个步骤。

1. 传递终止信号

首先是通过给goroutine传递关闭协程的信号,从而让协程进行退出操作。这里可以使用 context.Contex t来传递信号,具体实现可以通过调用 WithCancel 等方法来创建一个带有取消功能的Context,并在需要关闭协程时调用 Cancel 方法来向 Context 发送取消信号。示例代码如下:

ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
    for {
        select {
        // 调用cancel函数后,这里将能够收到通知,就能执行return
        case <-ctx.Done():
            return
        default:
            // do something
        }
    }
}(ctx)
// 在需要关闭协程时调用cancel方法发送取消信号
cancel()

2. 协程内部捕捉终止信号

协程内部也需要在取消信号传递过来时,将其成功捕捉到,才能够正常终止流程。这里我们可以使用select语句来监听取消信号。context对象的Done方法刚好也是返回一个channel,取消信号便是通过该channel来进行传递的

go func(ctx context.Context) {
    for {
        select {
        // 调用cancel函数后,这里将能够收到通知,并退出协程
        case <-ctx.Done():
            return
        default:
            // 执行业务逻辑
        }
    }
}(ctx)

3. 回收协程资源

在垃圾回收方面,Go语言采用的是自动垃圾回收(Garbage Collection,
GC)机制,它会定期扫描和回收不再被引用的内存对象。但是,GC只针对堆上的内存进行管理,对于其他类型的资源,如栈上的内存、文件句柄、数据库连接等,则需要程序员自己进行管理和释放。

当协程被终止执行时,其占用的资源,包括文件句柄、内存等,不会根据GC机制自动管理释放,需要我们主动管理释放,以便其他程序可以继续使用这些资源。这里我们使用defer语句来确保协程在退出时能够正确地释放资源。比如协程中打开了一个文件,此时可以通过defer语句来关闭,避免资源的泄漏。

func doWork() {
    file, err := os.Open("test.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    // Do some work
}

不关闭会有什么风险

1. 资源泄漏

当协程没有被正确关闭时,它可能会一直保持运行状态,并一直占用各种资源,如内存、CPU、网络连接、文件句柄等,影响程序整体性能和稳定性。

2. 内存泄漏

每个协程都有自己的栈空间,如果协程没有被正确关闭,其栈上分配的内存空间也无法被回收。这会导致程序内存消耗不断增加,直到耗尽系统内存。

3. 死锁和竞争条件

如果协程之间存在互斥锁(sync.Mutex)或其他同步原语的使用,而其中一个协程没有被正确关闭,就可能会导致死锁的发生。例子如下


```go
func main() {
	var wg sync.WaitGroup
	wg.Add(2)

	// 创建两个互斥锁
	mutex1 := &sync.Mutex{}
	mutex2 := &sync.Mutex{}

	// 启动两个协程
	go func() {
		defer wg.Done()

		// 获取mutex1锁
		mutex1.Lock()
		fmt.Println("协程1获取到mutex1锁")

		// 试图获取mutex2锁,但此时mutex2已被协程2持有
		mutex2.Lock()
		fmt.Println("协程1获取到mutex2锁")

		// 释放锁
		mutex2.Unlock()
		mutex1.Unlock()
	}()

	go func() {
		defer wg.Done()

		// 获取mutex2锁
		mutex2.Lock()
		fmt.Println("协程2获取到mutex2锁")

		// 试图获取mutex1锁,但此时mutex1已被协程1持有
		mutex1.Lock()
		fmt.Println("协程2获取到mutex1锁")

		// 释放锁
		mutex1.Unlock()
		mutex2.Unlock()
	}()

	wg.Wait()
	fmt.Println("程序结束")
}

协程1获取mutex1锁并输出"协程1获取到mutex1锁"。
协程2获取mutex2锁并输出"协程2获取到mutex2锁"。
协程1尝试获取mutex2锁,但此时mutex2已被协程2持有,所以协程1被阻塞。
协程2尝试获取mutex1锁,但此时mutex1已被协程1持有,所以协程2也被阻塞。
由于两个协程相互等待对方释放锁,程序进入死锁状态,无法继续执行。

本文参考原文:https://juejin.cn/post/7232824323751936055#heading-6

  • 17
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值