自学内容网 自学内容网

Go语言进阶之Context控制并发

Context

Context是Go语言中一个用于传递请求范围的上下文信息的标准库包,其主要用于处理并发操作中请求的生命周期的管理。

协程如何退出

利用协程退出的例子来说明Context的作用,以及没有使用Context,应该如何在没有执行完代码时提前退出协程

package main

import (
"fmt"
"time"
)

func main() {
// 创建一个用于退出的信号 channel
exitChan := make(chan struct{})

// 启动一个协程
go func() {
for {
select {
case <-exitChan:
fmt.Println("协程收到退出信号,正在退出...")
return // 退出协程
default:
// 模拟一些工作
fmt.Println("协程正在执行...")
time.Sleep(1 * time.Second) // 假装在做事情
}
}
}()

// 主协程等待一段时间后发送退出信号
time.Sleep(5 * time.Second)
close(exitChan) // 发送退出信号

// 等待一段时间,确保协程能够退出
time.Sleep(1 * time.Second)
fmt.Println("主协程结束")
}

这段代码使用了for select循环来中途暂停协程运行

当我们启动了一个处主协程之外的协程时,我们可以通过for select循环来选择停止协程与继续协程

虽然这段代码看上去并不长,并且十分好用,但现实中肯定不止这一个协程,如果想同时让很多个协程停止那么代码将会很长,所以这时就要使用Context了。

Context使用示例

将上面的代码使用Context库进行改造

package main

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

func main() {
// 创建一个带取消功能的上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保在 main 结束时调用取消

// 启动一个协程
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("协程收到退出信号,正在退出...")
return // 退出协程
default:
// 模拟一些工作
fmt.Println("协程正在执行...")
time.Sleep(1 * time.Second) // 假装在做事情
}
}
}()

// 主协程等待一段时间后发送退出信号
time.Sleep(5 * time.Second)
cancel() // 发送退出信号

// 等待一段时间,确保协程能够退出
time.Sleep(1 * time.Second)
fmt.Println("主协程结束")
}

在这段代码中使用 context.WithCancel创建一个可取消的上下文,其中cancel函数用于取消上下文。

当cancel函数被调用时,ctx.Done()会接收到结束的信号,并传递给case让进程结束。

Context同时处理多个协程

package main

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

func main() {
// 创建一个带取消功能的上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保在 main 结束时调用取消

var wg sync.WaitGroup

// 启动多个协程
numGoroutines := 3
for i := 1; i <= numGoroutines; i++ {
wg.Add(1) // 增加 WaitGroup 计数
go func(id int, ctx context.Context) {
defer wg.Done() // 协程完成时调用 Done
for {
select {
case <-ctx.Done():
fmt.Printf("协程 %d 收到退出信号,正在退出...\n", id)
return // 退出协程
default:
// 模拟一些工作
fmt.Printf("协程 %d 正在执行...\n", id)
time.Sleep(1 * time.Second) // 假装在做事情
}
}
}(i, ctx)
}

// 主协程等待一段时间后发送退出信号
time.Sleep(5 * time.Second)
cancel() // 发送退出信号

// 等待所有协程完成
wg.Wait()
fmt.Println("所有协程已结束,主协程结束")
}

Context详解

Context接口

Context接口方法主要有4种

1. Deadline() (deadline time.Time,ok bool)
// 这个方法可以获取设置的截止时间,第一个返回值deadline为截止时间,到了这个时间点,Context会自动发起取消请求,第二个返回值ok表示是否设置了截止时间

2. Done() <-chan struct{}
// 这个方法返回一个只读的通道,当上下文被取消时,这个通道就会被关闭。当方法返回的chan可以读取时,则意味着Context已经发起了取消信号。通过Done方法收到这个信号之后,就可以做清理操作,然后退出协程,释放资源

3. Err() error
// 这个方法返回上下文的错误状态,如果上下文被取消,返回context.Canceled;如果超时返回context.DeadlineExceeded

4. Value(key interface{}) interface{}
// 从上下文中获取与特定键关联的值,Value方法获取该Context上绑定的值,是一个键值对,所以要通过Key才可以获取。

上下文的创建(Context树)

上下文树的基本结构:

根上下文:通常使用context.Background() 或 context.TODO() 作为树的根节点。

子上下文:通过context.WithCancel(), context.WithTimeout(), 或 context.WithDeadline()创建的上下文是根上下文或其他上下文的子上下文。

上下文函数详解

Context主要提供了5种方法来创建新的上下文:

1. context.Background()
// 返回一个空上下文,通常做根上下文,通常在程序的最顶层使用,它可以作为其他上下文的父上下文

2. context.TODO()
// 当不确定使用哪个上下文时,可以使用TODO(),这个上下文的用途通常在代码开发的过程中,表示你需要稍后处理的上下文。

3. context.WithCancel(parent Context)
// 创建一个可取消的上下文,返回一个新上下文和一个取消函数。调用取消函数会取消这个上下文及其所以子上下文。

4. context.WithTimeout(parent Context,timeout time.Duration)
//创建一个带有超时的上下文。当超时时间达到,自动取消上下文

5. context.WithDeadline(parent Context,deadline time.Time)
// 与WithTimeout类似,但是使用绝对时间来设置截止时间
Context树的传播

在Context树中,父上下文的状态会影响到所有子上下文,当父上下文被取消是,所以的子上下嗯也会自动被取消。

package main

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

func main() {
    // 创建根上下文
    rootCtx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // 创建子上下文
    childCtx, childCancel := context.WithTimeout(rootCtx, 2*time.Second)
    defer childCancel()

    go func(ctx context.Context) {
        select {
        case <-ctx.Done():
            fmt.Println("子上下文被取消:", ctx.Err())
        }
    }(childCtx)

    // 模拟一些工作
    time.Sleep(1 * time.Second)
    
    // 取消根上下文
    cancel()
    
    // 等待子协程结束
    time.Sleep(1 * time.Second)
}

Context传值

Context在go中的作用不仅可以用于取消协程,还可以传值,通过这个能力Context储存的值可以供其他协程使用,这个方式适合传递请求范围内的共享数据。

context的值是通过context.WithValue函数设置的,其中传递的值是不可变的,并且使用interface{}类型实现

package main

import (
    "context"
    "fmt"
)

type key string

const userKey key = "user"

func main() {
    // 创建一个背景上下文
    ctx := context.Background()

    // 将值存入上下文
    ctx = context.WithValue(ctx, userKey, "Alice")

    // 在 goroutine 中使用上下文
    go func(ctx context.Context) {
        // 从上下文中获取值
        if user, ok := ctx.Value(userKey).(string); ok {
            fmt.Println("User from context:", user)
        } else {
            fmt.Println("User not found in context")
        }
    }(ctx)

    // 等待 goroutine 完成
    // 在实际应用中,使用 sync.WaitGroup 或其他同步机制
    select {}
}


原文地址:https://blog.csdn.net/2301_80148821/article/details/143693999

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!