深入理解Golang 中的Context包

news2024/11/28 22:39:31

context.Context是Go语言中独特的设计,在其他编程语言中我们很少见到类似的概念。context.Context深度支持Golang的高并发。

1.Goroutine 和channel。

在理解context包之前,应该首先熟悉Goroutine和Channel,能加深对context的理解。

1.1 Goroutine

Goroutine是一个轻量级的执行线程,多个Goroutine比一个线程轻量,所以管理Goroutine消耗的资源相对更少。Goroutine是Go中最基本的执行单元,每一个Go程序至少有一个Goroutine:主Goroutine。程序启动时会自动创建。为了能更好的理解Goroutine,先来看一看线程Thread与协程Coroutine的概念。

线程(Thread)

    线程是一种轻量级进程,是CPU调度的最小单位。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属于一个进程的其他线程共享进程所拥有的全部资源。线程拥有自己独立的栈和共享的堆,共享堆,不共享栈。线 程 的 切 换 一 般 由 操 作 系 统 调 度 \color{red}{线程的切换一般由操作系统调度}线程的切换一般由操作系统调度。

协程(Coroutine)

   协程又称为微线程,与子例程一样,协程也是一种程序组建,相对子例程而言,协程更为灵活,但在实践中使用没有子例程那样广泛。和线程类似,共享堆,不共享栈,协 程 的 切 换 一 般 由 程 序 员 在 代 码 中 显 式 控 制 \color{red}{协程的切换一般由程序员在代码中显式控制}协程的切换一般由程序员在代码中显式控制。他避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。Goroutine和其他语言的协程(coroutine)在使用方式上类似,但从字面意义上来看不同(一个是Goroutine,一个是coroutine),再就是协程是一种协作任务控制机制,在最简单的意义上,协程不是并发的,而Goroutine支持并发的。因此Goroutine可以理解为一种Go语言的协程。同时,Gorotine可以运行在一个或多个线程上。

使用示例

package main

import (

        "fmt"

)

func Hello() {

        fmt.Println("Hello everyBody, I'm WDS")

}

func main() {

        go Hello()

        fmt.Println("Example")

}

输出结果:

Example

Hello everyBody, I'm WDS

1.3Channel

  Channel就是多个Goroutine 之间的沟通渠道。当我们想要将结果或错误,或任何其他类型的信息从一个 goroutine 传递到另一个 goroutine 时就可以使用通道。通道是有类型的,可以是 int 类型的通道接收整数或错误类型的接收错误等。

  假设有个 int 类型的通道 ch,如果想发一些信息到这个通道,语法是 ch <- 1,如果想从这个通道接收一些信息,语法就是 var := <-ch。这将从这个通道接收并存储值到 var 变量。

通过改善1.2中的代码片段,证明通道的使用确保了 goroutine 执行完成并将值返回给 main 。

package main

import (

        "fmt"

)

func Hello(ch chan int) {

        fmt.Println("Hello everyBody, I'm WDS")

        ch <- 1

}

func main() {

        ch := make(chan int)

        go Hello(ch)

        <-ch

        fmt.Println("Example")

}

输出结果:

Hello everyBody, I'm WDS

Example

 

2.Context应用场景

package main

import (

        "fmt"

        "log"

        "net/http"

)

func main() {

        http.HandleFunc("/", SayHello) //设置访问的路由

        log.Fatalln(http.ListenAndServe(":8080", nil))

}

func SayHello(writer http.ResponseWriter, request *http.Request) {

        fmt.Println(&request)

        writer.Write([]byte("Hi,New Request Comes"))

}

上述代码每次请求,handler会创建一个Goroutine 为其提供服务、

    在真实应用场景中,每个请求对应的Handler,常会启动额外的的goroutine进行数据查询或PRC调用等。这里可以理解为每次请求的业务处理逻辑中,需要多次访问其他服务,而这些服务是可以并发执行的,即主Gorontine内的多个Goroutine并存。而且,当请求返回时,这些额外创建的goroutine需要及时回收。此外,一个请求对应一组请求域内的数据可能会被该请求调用链条内的各goroutine所需要现在对上面的代码添加一点东西,当请求进来时,hanler 创建一个监控goroutinue,这样每隔1s打印一个Hello World。

package main

import (

        "fmt"

        "log"

        "net/http"

        "time"

)

func main() {

        http.HandleFunc("/", SayHello) //设置访问的路由

        log.Fatalln(http.ListenAndServe(":8080", nil))

}

func SayHello(writer http.ResponseWriter, request *http.Request) {

        fmt.Println(&request)

        go func() {

                for range time.Tick(time.Second) {

                        fmt.Println("Current request is in progress")

                }

        }()

        time.Sleep(2 * time.Second)

        writer.Write([]byte("Hi,New Request Comes"))

}

在这里假定需要耗时2s,但在请求2s后返回,我们期望goroutine 在两次打印Hello World后停止。但运行发现,监控goroutine 打印后,其仍然不会结束,会一直打印下去。问题出在创建goroutine后,未对其生命周期作控制,下面我们使用context作一下控制,即监控程序打印前需检测request.Context()是否已经结束,若结束则退出循环,即结束生命周期。

示例代码:

package main

import (

        "fmt"

        "log"

        "net/http"

        "time"

)

func main() {

        http.HandleFunc("/", SayHello) //设置访问的路由

        log.Fatalln(http.ListenAndServe(":8080", nil))

}

func SayHello(writer http.ResponseWriter, request *http.Request) {

        fmt.Println(&request)

        go func() {

                for range time.Tick(time.Second) {

                        select {

                        case <-request.Context().Done():

                                fmt.Println("request is outgoing")

                                return

                        default:

                                fmt.Println("Hello World")

                        }

                }

        }()

        time.Sleep(2 * time.Second)

        writer.Write([]byte("Hi,New Request Comes"))

}

输出结果:

0xc00008e008

Hello World

request is outgoing

基于如上需求,context包应用而生。

context包可以提供一个请求从API请求边界到各goroutine的请求域数据传递、取消信号及截至时间等能力。

3.Context详解

   在 Go 语言中 context 包允许传递一个 “context” 到程序中。 Context 如超时或截止日期(deadline)或通道,来指示停止运行和返回。例如,如果正在执行一个 web 请求或运行一个系统命令,定义一个超时对生产级系统通常是个好主意。因为,如果依赖的API运行缓慢,不希望在系统上备份(back up)请求,因为它可能最终会增加负载并降低所有请求的执行效率。导致级联效应。这是超时或截止日期 context 派上用场的地方。

3.1设计原理

   Go 语言中的每一个请求的都是通过一个单独的 Goroutine 进行处理的,HTTP/RPC 请求的处理器往往都会启动新的 Goroutine 访问数据库和 RPC 服务,我们可能会创建多个 Goroutine 来处理一次请求,而 Context 的主要作用就是在不同的 Goroutine 之间同步请求特定的数据、取消信号以及处理请求的截止日期。

每一个 Context 都会从最顶层的 Goroutine 一层一层传递到最下层,这也是 Golang 中上下文最常见的使用方式,如果没有 Context,当上层执行的操作出现错误时,下层其实不会收到错误而是会继续执行下去:

当最上层的 Goroutine 因为某些原因执行失败时,下两层的 Goroutine 由于没有接收到这个信号所以会继续工作;但是当我们正确地使用 Context 时,就可以在下层及时停掉无用的工作减少额外资源的消耗:在这里,来看另一个例子:

package main

import (

        "context"

        "fmt"

        //        "log"

        //        "net/http"

        "time"

)

func main() {

        ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)

        defer cancel()

        go HelloHandle(ctx, 500*time.Millisecond)

        select {

        case <-ctx.Done():

                fmt.Println("Hello Handle", ctx.Err())

        }

}

func HelloHandle(ctx context.Context, duration time.Duration) {

        select {

        case <-ctx.Done():

                fmt.Println(ctx.Err())

        case <-time.After(duration):

                fmt.Println("Hello World with", duration)

        }

}

上面的代码,因为过期时间大运处理时间,所以我们有足够的时间处理改请求,输出结果如下:

Hello World with 500ms

Hello Handle context deadline exceeded

HelloHandle函数并没有进入超时的select分支,但是main函数的select却会等待context.Context的超时并打印出Hello Handle context deadline exceeded。如果我们将处理请求的时间增加至2000ms,程序就会因为上下文过期而被终止。

Hello Handle context deadline exceeded

context deadline exceeded

3.2 Context接口

context.Context 是 Go 语言在 1.7 版本中引入标准库的接口1,该接口定义了四个需要实现的方法,其中包括:

Deadline — 返回 context.Context 被取消的时间,也就是完成工作的截止日期;

Done — 返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个 Channel;

Err — 返回 context.Context 结束的原因,它只会在 Done 返回的 Channel 被关闭时才会返回非空的值;如果 context.Context 被取消,会返回 Canceled 错误;如果 context.Context 超时,会返回 DeadlineExceeded 错误;

Value — 从 context.Context 中获取键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法可以用来传递请求特定的数据;

type Context interface {

    Deadline() (deadline time.Time, ok bool)

    Done() <-chan struct{}

    Err() error

    Value(key interface{}) interface{}

}

4.总结

1context.Background 只应用在最高等级,作为所有派生 context 的根。

2.context取消是建议性的,这些函数可能需要一些时间来清理和退出。

3.不要把Context放在结构体中,要以参数的方式传递。

4.以Context作为参数的函数方法,应该把Context作为第一个参数,放在第一位。

5.给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用context.TODO

6.Context的Value相关方法应该传递必须的数据,不要什么数据都使用这个传递。

7.context.Value应该很少使用,它不应该被用来传递可选参数。这使得 API 隐式的并且可以引起错误。取而代之的是,这些值应该作为参数传递。

8.Context是线程安全的,可以放心的在多个goroutine中传递。同一个Context可以传给使用其的多个goroutine,且Context可被多个goroutine同时安全访问。

9.Context 结构没有取消方法,因为只有派生 context 的函数才应该取消 context。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/367600.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

苹果设计可变色Apple Watch表带,智能穿戴玩法多

苹果最新技术专利显示&#xff0c;苹果正在为 Apple Watch 设计一款可变色的表带&#xff0c;可以根据佩戴者所穿着的服装、所在的环境等自动改变颜色。据介绍&#xff0c;这款表带里的灯丝具有电致变色功能&#xff0c;可以通过施加不同的电压&#xff0c;来实现显示多种颜色或…

C++之类与对象(上)

目录 一、类的定义 二.类的访问限定及封装 1.访问限定 2.封装 三.类的作用域和实例化 2.类的实例化 四.类的对象大小的计算 1.类成员存储方式 2.结构体内存对齐规则 五.类成员函数的this指针 1.this指针的引出 2.this指针的特性 3.C语言和C实现Stack的对比 一、类的定义 class …

Linux的kdump分析

文章目录一 系统环境二 下载和安装kerner-debuginfo三 启动crash四 crash常用命令一 系统环境 进行kdump分析的主机是CentOS-7.9系统。 [rootcanway ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [rootcanway ~]# uname -r 3.10.0-1160.el7.x86_64检查…

智慧新零售网络解决方案,助力新零售企业数智化转型

随着数字化时代的不断发展&#xff0c;新零售连锁业务模式“线上线下”融合发展&#xff0c;数据、设备在逐渐增加&#xff0c;门店数量也会随着企业规模的扩大而增加&#xff0c;但由于传统网络架构不稳定、延时、容量小影响服务质量&#xff08;QoS&#xff09;、分支设备数量…

【java】Spring Cloud --Spring Cloud Alibaba 教程

文章目录Spring Cloud Alibaba是什么Spring Cloud AlibabaSpring Cloud Alibaba 组件Spring Cloud Alibaba 的应用场景Spring Cloud 两代实现组件对比Spring Cloud Alibaba 版本依赖Spring Cloud Alibaba 组件版本关系Spring Cloud Alibaba NacosNacos 的特性服务发现服务健康监…

写博客有哪些好用的工具和软件?

写博客有哪些好用的工具和软件&#xff1f; 在各种平台上写文章&#xff0c;要有一个好用的编辑器&#xff0c;使用简单方便、不用花很多时间排版、能预览效果等。此外&#xff0c;文章还需要配图片、动图和视频等&#xff0c;所以需要好用的截图软件、视频录制和剪辑软件。 …

ME1M 报表Layout调整

众所周知&#xff0c;SAP的许多报表都可以选择以ALV的格式输出&#xff0c;ALV格式界面友好&#xff0c;业务人员可以按照自己的需求去调整报表输出格式&#xff0c;同时也方便导出到本地文件。大多数报表的选择界面上都有一个字段scope of list, 将其改成ALV&#xff1a; 偏偏…

该学会是自己找bug了(vs调试技巧)

前言 &#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f; c语言初阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:介绍c语言初阶的最后一篇.有关调试的重要性. 金句分享…

Linux基本介绍与常用操作指令

参考链接&#xff1a; Linux面试必备20个常用命令_无 羡ღ的博客-CSDN博客_linux常用命令 1. Linux简介 Linux是一个支持多用户、多任务、多线程和多CPU的操作系统&#xff0c;特点是免费、稳定、高效&#xff0c; 一般运行在大型服务器上。 1.1 常用目录简介 /&#xff1a;根目…

【storybook】你需要一款能在独立环境下开发组件并生成可视化控件文档的框架吗?(一)

storybook介绍入门说说用法prop-types.stories.jsx介绍 你肯定用过ant-design或者elemenUI中的一款UI组件库&#xff0c; 你肯定打开过它们的官网&#xff0c; 你是否好奇过&#xff0c;页面上展示的案例&#xff0c; 你为啥可以操作&#xff0c;还能查看源码供你CV?它们是…

Docker之路(6.docker容器数据卷、具名挂载匿名挂载、权限挂载)

1. 什么是容器数据卷 如果数据都在容器中&#xff0c;那么容器删除后&#xff0c;数据就会丢失&#xff01; 所以希望容器的数据能够存到本地 容器之间可以又一个数据共享的技术&#xff01; Docker容器中产生的数据&#xff0c;可以同步到本地&#xff0c;这就是卷技术。 就是…

Nacos Ignore the empty nacos configuration and get it based on dataId

1.配置错误 dataId问题 启动日志&#xff1a; 使用properties格式的文件&#xff1a; Ignore the empty nacos configuration and get it based on dataId[xxx-server] & group[DEFAULT_GROUP] Ignore the empty nacos configuration and get it based on dataId[xxx-s…

【Redis中bigkey你了解吗?bigkey的危害?】

一.Redis中bigkey你了解吗&#xff1f;bigkey的危害&#xff1f; 如果面试官问到了这个问题&#xff0c;不必惊慌&#xff0c;接下来我们从什么是bigkey&#xff1f;bigkey划分的类型&#xff1f;bigkey危害之处&#xff1f; 二.什么是bigkey&#xff1f;会有什么影响&#xff…

九头蛇hydra爆破http示例

使用hydra执行http表单暴力破解 通过浏览器自带分析得知: 提交地址:http://10.0.0.115/student_attendance/ajax.php?action=login 提交方式:POST 提交数据:username=a&password=a 服务响应:3 根据以上收集的信息就可以使用hydra进行密码爆破 hydra 10.0.0.115 http…

【第一章:Spring概述、特点、IOC容器、IOC操作bean管理(基于xml方式创建对象,注入属性)】

第一章&#xff1a;Spring概述、特点、IOC容器、IOC操作bean管理&#xff08;基于xml方式创建对象&#xff0c;注入属性&#xff09; 1.Spring是什么&#xff1f; ①Spring是一款主流的java EE 轻量级开源框架。 ②广义的Spring&#xff1a;Spring技术栈&#xff0c;Spring不再…

华为OD机试题,用 Java 解【计算最大乘积】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…

【基础算法】哈希表(开放寻址法)

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

聚观早报|饿了么星选停止运营;百度2022财报全年营收1236.75亿元

今日要闻&#xff1a;饿了么星选停止运营&#xff1b;百度2022财报全年营收1236.75亿元&#xff1b;微软收购暴雪获力挺&#xff1b;全国首例&#xff01;这一代练平台被叫停&#xff1b;现代汽车已开始在美国生产电动汽车 饿了么星选停止运营 2 月 22 日&#xff0c;饿了么旗下…

使用Vite创建VUE项目

使用Vite来创建vue项目 很久没有接触前端的技术了&#xff0c;一下子就冒出来很多技术&#xff0c;有的根本没见过呀&#xff0c;vue变成了3&#xff0c;vue-cli也变得不好用了&#xff0c;说vite很好用&#xff0c;那就创建个项目试试吧。 有一点前提哈&#xff0c;电脑上要…

华为OD机试题,用 Java 解【磁盘容量排序】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…