设为首页 加入收藏

TOP

如何优雅得关闭协程呢(二)
2023-07-23 13:28:04 】 浏览:96
Tags:何优雅 关闭协 程呢
使用一个default分支在协程中执行具体的业务逻辑。在终止信号没有到来时,就执行业务逻辑;在收到协程终止信号后,也能够及时终止协程的执行。如下:

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

3.3 回收协程资源

最后,当协程被终止执行时,需要释放占用的资源,包括文件句柄、内存等,以便其他程序可以继续使用这些资源。在Go语言中,可以使用defer语句来确保协程在退出时能够正确地释放资源。比如协程中打开了一个文件,此时可以通过defer语句来关闭,避免资源的泄漏。代码示例如下:

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

    // Do some work
}

在这个例子中,我们在文件打开之后使用defer语句注册了一个函数,当协程结束时会自动调用该函数来关闭文件。这样协程无论在何时退出,我们都可以确保文件被正确关闭,避免资源泄漏和其他问题。

3.4 关闭goroutine示例

下面展示一个简单的例子,结合Context对象,select语句以及defer语句这三部分内容,优雅得终止一个协程的运行,具体代码示例如下:

package main

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

func worker(ctx context.Context) {
    // 最后,在协程退出前,释放资源.
    defer fmt.Println("worker stopped")

    for {
        // 通过select语句监听取消信号,取消信号没到达,则执行业务逻辑,等下次循环检查
        select {
        default:
            fmt.Println("working")
        case <-ctx.Done():
            return
        }
        time.Sleep(time.Second)
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    // 启动一个协程执行任务
    go worker(ctx)
    // 执行5s后,调用cancel函数终止协程
    time.Sleep(5 * time.Second)
    cancel()

    time.Sleep(2 * time.Second)
}

main函数中,我们使用context.WithCancel函数创建了一个新的context,并将其传递给worker函数,同时启动协程运行worker函数。

worker函数执行5s后,主协程调用cancel函数来终止worker协程。之后,worker协程中监听取消信号的select语句,将能够捕捉到这个信号,执行终止协程操作。

最后,在退出协程时,通过defer语句实现资源的释放。综上,我们实现了协程的优雅关闭,同时也正确回收了资源。

4. 需要主动关闭协程运行的常见场景

4.1 协程在执行一个不断重复的任务

协程在执行一个不断重复的任务时,此时协程是不会主动终止运行的。但是在某个时刻之后,不需要再继续执行该任务了,需要主动关闭goroutine的执行,释放协程的资源。

这里以etcd为例来进行说明。etcd主要用于在分布式系统中存储配置信息、元数据和一些小规模的共享数据。也就是说,我们可以在etcd当中存储一些键值对。那么,如果我们想要设置键值对的有效期,那该如何实现呢?

etcd中存在一个租约的概念,租约可以看作是一个时间段,该时间段内某个键值对的存在是有意义的,而在租约到期后,该键值对的存在便没有意义,可以被删除,同时一个租约可以作用于多个键值对。下面先展示如何将一个租约和一个key进行关联的示例:

// client 为 etcd客户端的连接,基于此建立一个Lease实例
// Lease示例提供一些api,能过创建租约,取消租约,续约租约
lease := clientv3.NewLease(client)

// 创建一个租约,同时租约时间为10秒
grantResp, err := lease.Grant(context.Background(), 10)
if err != nil {
    log.Fatal(err)
}
// 租约ID,每一个租约都有一个唯一的ID
leaseID := grantResp.ID

// 将租约与key进行关联,此时该key的有效期,也就是该租约的有效期
_, err = kv.Put(context.Background(), "key1", "value1", clientv3.WithLease(leaseID))
if err != nil {
    log.Fatal(err)
}

以上代码演示了如何在etcd中创建一个租约并将其与一个键值对进行关联。首先,通过etcd客户端的连接创建了一个Lease实例,该实例提供了一些api,可以创建租约、取消租约和续约租约。然后使用Grant函数创建了一个租约并指定了租约的有效期为10秒。接下来,获取租约ID,每个租约都有一个唯一的ID。最后,使用Put函数将租约与key进行关联,从而将该key的有效期设定为该租约的有效期。

所以,我们如果想要操作etcd中键值对的有效期,只需要操作租约的有效期即可。

而刚好,etcd其实定义了一个Lease接口,该接口定义了对租约的一些操作,能过创建租约,取消租约,同时也支持续约租约,获取过期时间等内容,具体如下:

type Lease interface {
   // 1. 创建一个新的租约
   Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error)
   // 2. 取消租约
   Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error)
   // 3. 获取租约的剩余有效期
   TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error)
   // 4. 获取所有的租约
   Leases(ctx context.Context) (*LeaseLeasesResponse, error)
   // 5. 不断对租约进行续约,这里假设10s后过期,此时大概的含义为每隔10s续约一次租约,调用该方法后,租约将永远不会过期  
   KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error)
   // 6. 续约一次租约
   KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveRespons
首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇go实现在线翻译功能小项目 下一篇defer有什么用呢

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目