设为首页 加入收藏

TOP

如何优雅得关闭协程呢(一)
2023-07-23 13:28:04 】 浏览:95
Tags:何优雅 关闭协 程呢

1.简介

本文将介绍首先为什么需要主动关闭goroutine,并介绍如何在Go语言中关闭goroutine的常见套路,包括传递终止信号和协程内部捕捉终止信号。之后,文章列举了需要主动关闭协程运行的常见场景,如启动一个协程执行一个不断重复的任务。希望通过本文的介绍,读者能够掌握如何在适当的时候关闭goroutine,以及了解关闭goroutine的常见套路。

2.为什么需要关闭goroutine

2.1 协程的生命周期

了解协程的生命周期是优雅地关闭协程的前提,因为在关闭协程之前需要知道协程的当前状态,以便采取相应的措施。所以这里我们需要先了解下goroutine的生命周期。

Go语言中,协程(goroutine)是一种轻量级的线程,可以在一个程序中同时运行多个协程,提高程序的并发性能。协程的生命周期包括创建、运行和结束三个阶段。

首先需要创建一个协程,协程的创建可以通过关键字 go 来实现,例如:

go func() {
    // 协程执行的代码
}()

上面的代码会启动一个新的协程,同时在新的协程中执行匿名函数,此时协程便已被创建了。

一旦协程被创建,它就会在新的线程中运行。协程的运行状态可以由 Go 运行时(goroutine scheduler)来管理,它会自动将协程调度到适当的P中运行,并确保协程的公平调度和平衡负载。

在运行阶段,协程会不断地执行任务,直到任务完成或者遇到终止条件。在终止阶段,协程将会被回收,从而完成其整个生命周期。

综上所述,协程由go关键字启动,在协程中执行其业务逻辑,直到最后遇到终止条件,此时代表着协程的任务已经结束了,将进入终止阶段。最终协程将会被回收。

2.2 协程的终止条件

正常来说,都是协程任务执行完成之后,此时协程自动退出,例如:

func main() {
   var wg sync.WaitGroup
   wg.Add(1)
   go func() {
      defer wg.Done()
      // 协程执行的代码
      fmt.Println("协程执行完毕")
   }()
   wg.Wait()
   // 等待协程执行完毕
   fmt.Println("主程序结束")

上面的代码中,我们使用 WaitGroup 等待协程执行完毕。在协程执行完毕后,程序会输出协程执行完毕和主程序结束两条信息。

还有一种情况是协程发生panic,它将会自动退出。例如:

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        // 协程执行的代码
        panic("协程发生错误")
    }()
    // 等待协程执行完毕
    wg.Wait()
    fmt.Println("主程序结束")
}

在这种情况下,协程也会自动退出,不会再占用系统资源。

综合看来,协程的终止条件,其实就是协程中的任务执行完成了,或者是执行过程中发生了panic,协程将满足终止条件,退出执行。

2.3 为什么需要主动关闭goroutine

从上面协程的终止条件来看,正常情况下,协程只要将任务正常处理完成,协程自动退出,此时并不需要主动关闭goroutine

这里先举一个生产者消费者的例子,在这个例子中,我们创建了一个生产者和一个消费者,它们之间通过一个channel进行通信。生产者生产数据并发送到一个channel中,消费者从这个channel中读取数据并进行处理。代码示例如下:

func main() {
    // 生产者代码
    go func(out chan<- int) {
        for i := 0; ; i++ {
            select {
            case out <- i:
                fmt.Printf("producer: produced %d\n", i)
            time.Sleep(time.Second)
        }
    }
    // 消费者逻辑
    go func(in <-chan int) {
        for {
            select {
            case i := <-in:
                fmt.Printf("consumer: consumed %d\n", i)
            }
        }
    }
    // 让生产者协程和消费者协程一直执行下去
    time.Sleep(100000000)
}

在这个例子中,我们使用了两个goroutine:生产者和消费者。生产者向channel中生产数据,消费者从channel中消费数据。

但是,假如生产者出现了问题,此时生产者的协程将会被退出,不再执行。而消费者仍然在等待数据的输入。此时消费者协程已经没有存在的必要了,其实是需要退出执行。

因此,对于一些虽然没有达到终止条件的协程,但是其又没有再继续执行下去的必要,此时主动关闭其执行,从而保证程序的健壮性和性能。

3.如何优雅得关闭goroutine

优雅得关闭goroutine的执行,我们可以遵循以下三个步骤。首先是传递关闭协程的信号,其次是协程内部需要能够到关闭信号,最后是协程退出时,能够正确释放其所占据的资源。通过以上步骤,可以保在需要时优雅地停止goroutine的执行。下面对这三个步骤详细进行讲解。

3.1 传递关闭终止信号

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

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

这里,当我们想要终止协程的执行时,只需要调用可取消context对象的Cancel方法,协程内部将能够通过context对象接收到终止协程执行的通知。

3.2 协程内部捕捉终止信号

协程内部也需要在取消信号传递过来时,能够正确被捕捉到,才能够正常终止流程。这里我们可以使用select语句来监听取消信号。select语句可以有多个case子句,可以同时监听多个channel,当select语句执行时,它会一直阻塞,直到有一个case子句可以执行。select语句也可以包含default子句,这个子句在所有的case子句都不能执行时会被执行,通常用于防止select语句的阻塞。如下:

select {
case <-channel:
    // channel有数据到来时执行的代码
default:
    // 所有channel都没有数据时执行的代码
}

context对象的Done方法刚好也是返回一个channel,取消信号便是通过该channel来进行传递的。所以我们可以在协程内部,通过select语句,在其中一个case分支来监听取消信号;同时

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇go实现在线翻译功能小项目 下一篇defer有什么用呢

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目