Go语言中的Context:优雅地处理并发与超时

context

Context专门用来简化 对于处理单个请求的多个 goroutine 之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个 API 调用。

context.Context是一个接口,该接口定义了四个需要实现的方法
其中:

  • Deadline方法需要返回当前Context被取消的时间,也就是完成工作的截止时间(deadline);
  • Done方法需要返回一个Channel,这个Channel会在当前工作完成或者上下文被取消之后关闭,多次调用Done方法会返回同一个Channel;
  • Err方法会返回当前Context结束的原因,它只会在Done返回的Channel被关闭时才会返回非空的值;
    • 如果当前Context被取消就会返回Canceled错误;
    • 如果当前Context超时就会返回DeadlineExceeded错误;
  • Value方法会从Context中返回键对应的值,对于同一个上下文来说,多次调用Value 并传入相同的Key会返回相同的结果,该方法仅用于传递跨API和进程间跟请求域的数据;

数据传递 WithValue

context 的一个常见用途是传递请求域的数据。例如,我们可以通过 context.WithValue 将一些数据附加到 Context 中,并在后续的函数调用中通过 Value 方法获取这些数据。

package main

import (
	"context"
	"fmt"
)

func main() {
	ctx := context.Background()
	ctx = context.WithValue(ctx, "name", "dgg")
	GetUser(ctx)

}
func GetUser(ctx context.Context) {

	fmt.Println(ctx.Value("name"))

}
## 取消协程 WithCancel
```go
package main

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

// 两秒钟停掉
var wg sync.WaitGroup

func main() {
	t1 := time.Now()
	ctx, cancel := context.WithCancel(context.Background())
	wg.Add(1)
	go func() {

		ip, err := GetIp1(ctx)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(ip)

	}()
	go func() {
		time.Sleep(2 * time.Second)
		//取消协程
		cancel()
		wg.Done()

	}()
	wg.Wait()
	fmt.Println("执行完成", time.Since(t1))

}
func GetIp1(ctx context.Context) (ip string, err error) {
	select {
	case <-ctx.Done():
		fmt.Println("协程取消", ctx.Err())
		return "", ctx.Err()
	case <-time.After(4 * time.Second):
		ip = "11111"
		return ip, nil
	}
}

在这个例子中,我们通过 context.WithValue 将 “name” 键对应的值 “dgg” 附加到 Context 中,然后在 GetUser 函数中通过 ctx.Value(“name”) 获取这个值。

取消协程 WithCancel

context.WithCancel 是一个非常有用的函数,它允许我们创建一个可以被取消的 Context。当调用 cancel 函数时,Context 会被取消,Done 方法返回的 Channel 会被关闭。

package main

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

// 两秒钟停掉
var wg sync.WaitGroup

func main() {
	t1 := time.Now()
	ctx, cancel := context.WithCancel(context.Background())
	wg.Add(1)
	go func() {

		ip, err := GetIp1(ctx)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(ip)

	}()
	go func() {
		time.Sleep(2 * time.Second)
		//取消协程
		cancel()
		wg.Done()

	}()
	wg.Wait()
	fmt.Println("执行完成", time.Since(t1))

}
func GetIp1(ctx context.Context) (ip string, err error) {
	select {
	case <-ctx.Done():
		fmt.Println("协程取消", ctx.Err())
		return "", ctx.Err()
	case <-time.After(4 * time.Second):
		ip = "11111"
		return ip, nil
	}
}

在这个例子中,我们创建了一个可以被取消的 Context,并在另一个 goroutine 中调用了 cancel 函数。当 cancel 函数被调用时,GetIp1 函数会收到取消信号,并立即返回错误。

截至时间 WithDeadline

context.WithDeadline 允许我们为 Context 设置一个截止时间。当截止时间到达时,Context 会被自动取消。

package main

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

// 两秒钟停掉
var wg1 sync.WaitGroup

func main() {
	t1 := time.Now()
	ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(2*time.Second))
	wg1.Add(1)
	go func() {

		ip, err := GetIp2(ctx)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(ip)

	}()

	wg1.Wait()
	fmt.Println("执行完成", time.Since(t1))

}
func GetIp2(ctx context.Context) (ip string, err error) {
	go func() {
		select {
		case <-ctx.Done():
			fmt.Println("协程取消", ctx.Err())
			err = ctx.Err()
			wg1.Done()
			return
		}
	}()

	time.Sleep(4 * time.Second)
	ip = "11111"
	wg1.Done()
	return
}

在这个例子中,我们为 Context 设置了一个截止时间,当截止时间到达时,GetIp2 函数会收到取消信号,并立即返回错误。

超时时间 WithTimeout

context.WithTimeout 是 WithDeadline 的一个特例,它允许我们为 Context 设置一个超时时间。当超时时间到达时,Context 会被自动取消。

package main

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

var wg2 sync.WaitGroup

func main() {
	t1 := time.Now()
	// 使用 WithTimeout 设置超时时间为 2 秒
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel() // 确保在函数退出时调用 cancel 函数[^1^]

	wg2.Add(1)
	go func() {
		defer wg2.Done()
		ip, err := GetIp3(ctx)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(ip)
	}()

	wg2.Wait()
	fmt.Println("执行完成", time.Since(t1))
}

func GetIp3(ctx context.Context) (ip string, err error) {
	// 使用 select 语句监听 ctx.Done() 和 time.After()
	select {
	case <-ctx.Done():
		fmt.Println("协程取消", ctx.Err())
		return "", ctx.Err() // 如果上下文被取消,立即返回错误[^1^]
	case <-time.After(4 * time.Second):
		ip = "11111"
		return ip, nil
	}
}

在这个例子中,我们为 Context 设置了一个超时时间,当超时时间到达时,GetIp3 函数会收到取消信号,并立即返回错误。

总结

context 包是Go语言并发编程中的一个重要工具。它通过提供 WithValue、WithCancel、WithDeadline 和 WithTimeout 等函数,让我们可以优雅地处理并发任务中的数据传递、取消信号和超时问题。在实际开发中,合理使用 context 可以让我们的代码更加简洁、高效和健壮。

你可能感兴趣的:(golang,开发语言)