深入理解Golang中的Context包

2023-05-16

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可以运行在一个或多个线程上

1.2 使用Goroutine示例

func Hello()  {
    fmt.Println("hello everybody , I'm lineshen")
}

func main()  {
    go Hello()
    fmt.Println("Golang-Gorontine Example")
}

在该代码片段中,使用go又开启了一个Goroutine执行Hello方法,其运行结果如下:

Golang-Gorontine Example

从执行结果上,直观的看,我们的程序似乎没有执行Goroutine的Hello方法。出现这个问题的原因是我们启动的主Goroutine在main执行完就退出了,所以为了main等待Hello-Goroutine执行完,就需要一些方法,让Hello-Goroutine告诉main执行完了,这里就需要通道Channel了。

1.3 Channel

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

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

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

func Hello(ch chan int)  {
    fmt.Println("hello everybody , I'm lineshen")
    ch <- 1
}

func main()  {
    ch := make(chan int)
    go Hello(ch)
    <-ch
    fmt.Println("Golang-Gorontine Example")
}

执行结果如下:

hello everybody , I'm lineshen
Golang-Gorontine Example

这里我们使用通道进行等待,这样main就会等待Hello-Goroutine执行完。熟悉了Goroutine、Channel的概念了,就很好理解Context了。

2. Context应用场景

熟悉了Goroutine、Channel的概念,先看一个请求服务场景示例:

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为其提供服务;
如果连续请求3次,request的地址也是不同的:

$ curl http://localhost:8080/
0xc0000b8030
0xc000186008
0xc000186018

在真实应用场景中,每个请求对应的Handler,常会启动额外的的goroutine进行数据查询或PRC调用等。这里可以理解为每次请求的业务处理逻辑中,需要多次访问其他服务,而这些服务是可以并发执行的,即主Gorontine内的多个Goroutine并存。而且,当请求返回时,这些额外创建的goroutine需要及时回收。此外,一个请求对应一组请求域内的数据可能会被该请求调用链条内的各goroutine所需要。

现在对上面代码在添加一点东西,当请求进来时,Handler创建一个监控goroutine,这样就会每隔1s打印一句Current request is in progress:

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在打印2次Current request is in progress后即停止。但运行发现,监控goroutine打印2次后,其仍不会结束,而会一直打印下去。

问题出在创建监控goroutine后,未对其生命周期作控制,下面我们使用context作一下控制,即监控程序打印前需检测request.Context()是否已经结束,若结束则退出循环,即结束生命周期。

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("Current request is in progress")
            }
        }
    }()

    time.Sleep(2 * time.Second)
    writer.Write([]byte("Hi, New Request Comes"))
}

基于如上需求,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 时,就可以在下层及时停掉无用的工作减少额外资源的消耗:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210221230717934.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NoZW56aWhlbmcx,size_16,color_FFFFFF,t_70#pic_center = 600x300)
这其实就是 Golang 中上下文的最大作用,在不同 Goroutine 之间对信号进行同步避免对计算资源的浪费,与此同时 Context 还能携带以请求为作用域的键值对信息。

这里光说,其实也不能完全理解其中的作用,所以我们来看一个例子:

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("process request with", duration)
    }

}

上面的代码,因为过期时间大于处理时间,所以我们有足够的时间处理改请求,所以运行代码如下图所示:

process request with 500ms
Hello Handle  context deadline exceeded

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

context deadline exceeded
Hello Handle  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{}
}

3.2.1 创建context

context包允许一下方式创建和获得context:

  • context.Background():这个函数返回一个空context。这只能用于高等级(在 main 或顶级请求处理中)。
  • context.TODO():这个函数也是创建一个空context。也只能用于高等级或当您不确定使用什么 context,或函数以后会更新以便接收一个 context 。这意味您(或维护者)计划将来要添加 context 到函数。

其实我们查看源代码。发现他俩都是通过 new(emptyCtx) 语句初始化的,它们是指向私有结构体 context.emptyCtx 的指针,这是最简单、最常用的上下文类型:

var (
    background = new(emptyCtx)
    todo       = new(emptyCtx)
)
type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
    return
}

func (*emptyCtx) Done() <-chan struct{} {
    return nil
}

func (*emptyCtx) Err() error {
    return nil
}

func (*emptyCtx) Value(key interface{}) interface{} {
    return nil
}

func (e *emptyCtx) String() string {
    switch e {
    case background:
        return "context.Background"
    case todo:
        return "context.TODO"
    }
    return "unknown empty Context"
}

从上述代码,我们不难发现 context.emptyCtx 通过返回 nil 实现了 context.Context 接口,它没有任何特殊的功能。

从源代码来看,context.Background 和 context.TODO 函数其实也只是互为别名,没有太大的差别。它们只是在使用和语义上稍有不同:

  • context.Background 是上下文的默认值,所有其他的上下文都应该从它衍生(Derived)出来。
  • context.TODO 应该只在不确定应该使用哪种上下文时使用;
    在多数情况下,如果当前函数没有上下文作为入参,我们都会使用 context.Background 作为起始的上下文向下传递。

3.2.2 context的继承衍生

有了如上的根Context,那么是如何衍生更多的子Context的呢?这就要靠context包为我们提供的With系列的函数了。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context

这四个With函数,接收的都有一个partent参数,就是父Context,我们要基于这个父Context创建出子Context的意思,这种方式可以理解为子Context对父Context的继承,也可以理解为基于父Context的衍生。

通过这些函数,就创建了一颗Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个。

WithCancel函数,传递一个父Context作为参数,返回子Context,以及一个取消函数用来取消Context。 WithDeadline函数,和WithCancel差不多,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消Context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消。

WithTimeout和WithDeadline基本上一样,这个表示是超时自动取消,是多少时间后自动取消Context的意思。

WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到.

  • WithValue
    context 包中的 context.WithValue 函数能从父上下文中创建一个子上下文,传值的子上下文使用 context.valueCtx 类型,我们看一下源码:
// stringify tries a bit to stringify v, without using fmt, since we don't
// want context depending on the unicode tables. This is only used by
// *valueCtx.String().
func stringify(v interface{}) string {
    switch s := v.(type) {
    case stringer:
        return s.String()
    case string:
        return s
    }
    return "<not Stringer>"
}

func (c *valueCtx) String() string {
    return contextName(c.Context) + ".WithValue(type " +
        reflectlite.TypeOf(c.key).String() +
        ", val " + stringify(c.val) + ")"
}

func (c *valueCtx) Value(key interface{}) interface{} {
    if c.key == key {
        return c.val
    }
    return c.Context.Value(key)
}

此函数接收 context 并返回派生 context,其中值 val 与 key 关联,并通过 context 树与 context 一起传递。这意味着一旦获得带有值的 context,从中派生的任何 context 都会获得此值。不建议使用 context 值传递关键参数,而是函数应接收签名中的那些值,使其显式化。

context.valueCtx 结构体会将除了 Value 之外的 Err、Deadline 等方法代理到父上下文中,它只会响应 context.valueCtx.Value 方法。如果 context.valueCtx 中存储的键值对与 context.valueCtx.Value 方法中传入的参数不匹配,就会从父上下文中查找该键对应的值直到在某个父上下文中返回 nil 或者查找到对应的值。

实际使用示例:

type key string

func main()  {
    ctx := context.WithValue(context.Background(),key("lineshen"),"tencent")
    Get(ctx,key("lineshen"))
    Get(ctx,key("line"))
}

func Get(ctx context.Context,k key)  {
    if v, ok := ctx.Value(k).(string); ok {
        fmt.Println(v)
    }
}

上面代码我们基于context.Background创建一个带值的ctx,然后可以根据key来取值。这里为了避免多个包同时使用context而带来冲突,key不建议使用string或其他内置类型,所以建议自定义key类型.

  • WithCancel
    此函数创建从传入的父 context 派生的新 context。父 context 可以是后台 context 或传递给函数的 context。返回派生 context 和取消函数。只有创建它的函数才能调用取消函数来取消此 context。如果您愿意,可以传递取消函数,但是,强烈建议不要这样做。这可能导致取消函数的调用者没有意识到取消 context 的下游影响。可能存在源自此的其他 context,这可能导致程序以意外的方式运行。简而言之,永远不要传递取消函数。

  • WithDeadline
    此函数返回其父项的派生 context,当截止日期超过或取消函数被调用时,该 context 将被取消。例如,您可以创建一个将在以后的某个时间自动取消的 context,并在子函数中传递它。当因为截止日期耗尽而取消该 context 时,获此 context 的所有函数都会收到通知去停止运行并返回。

我们来看一下源码:

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
    if cur, ok := parent.Deadline(); ok && cur.Before(d) {
        return WithCancel(parent)
    }
    c := &timerCtx{
        cancelCtx: newCancelCtx(parent),
        deadline:  d,
    }
    propagateCancel(parent, c)
    dur := time.Until(d)
    if dur <= 0 {
        c.cancel(true, DeadlineExceeded) // 已经过了截止日期
        return c, func() { c.cancel(false, Canceled) }
    }
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.err == nil {
        c.timer = time.AfterFunc(dur, func() {
            c.cancel(true, DeadlineExceeded)
        })
    }
    return c, func() { c.cancel(true, Canceled) }
}

context.WithDeadline也都能创建可以被取消的计时器上下文 context.timerCtx:

context.WithDeadline 方法在创建 context.timerCtx 的过程中,判断了父上下文的截止日期与当前日期,并通过 time.AfterFunc 创建定时器,当时间超过了截止日期后会调用 context.timerCtx.cancel 方法同步取消信号。

  • WithTimeout
    此函数类似于 context.WithDeadline。不同之处在于它将持续时间作为参数输入而不是时间对象。此函数返回派生 context,如果调用取消函数或超出超时持续时间,则会取消该派生 context。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
    return WithDeadline(parent, time.Now().Add(timeout))
}

观看源码我们可以看出WithTimeout内部调用的就是WithDeadline,其原理都是一样的,上面已经介绍过了,来看一个例子吧:

func main()  {

    ctx,cancel := context.WithTimeout(context.Background(),10 * time.Second)
    defer cancel()
    go Monitor(ctx)

    time.Sleep(20 * time.Second)

}

func Monitor(ctx context.Context)  {
    select {
    case <- ctx.Done():
        fmt.Println(ctx.Err())
    case <-time.After(20*time.Second):
        fmt.Println("stop monitor")
    }
}

4. 总结:Context使用原则

  1. context.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。

Go 语言中的 context.Context 的主要作用还是在多个 Goroutine 组成的树中同步取消信号以减少对资源的消耗和占用,虽然它也有传值的功能,但是这个功能我们还是很少用到。在真正使用传值的功能时我们也应该非常谨慎,使用 context.Context 进行传递参数请求的所有参数一种非常差的设计,比较常见的使用场景是传递请求对应用户的认证令牌以及用于进行分布式追踪的请求 ID。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入理解Golang中的Context包 的相关文章

  • Go内存管理及性能观测工具

    内存管理 TCMalloc Golang内存分配算法主要源自Google的TCMalloc算法 TCMalloc将内存分成三层最外层Thread Cache 中间层Central Cache 最里层Page Heap Thread Cach
  • Go的并发的退出

    有时候我们需要通知goroutine停止它正在干的事情 比如一个正在执行计算的web服务 然而它的客户端已经断开了和服务端的连接 Go语言并没有提供在一个goroutine中终止另一个goroutine的方法 由于这样会导致goroutin
  • Go开发命令行程序指南

    近期在Twitter上看到一个名为 Command Line Interface Guidelines 的站点 1 这个站点汇聚了帮助大家编写出更好命令行程序的哲学与指南 这份指南基于传统的Unix编程原则 2 又结合现代的情况进行了 与时
  • Go语言实现区块链与加密货币-Part3(交易优化,单机模拟多节点通信)

    交易 二 在这个系列文章的一开始 我们就提到了 区块链是一个分布式数据库 不过在之前的文章中 我们选择性地跳过了 分布式 这个部分 而是将注意力都放到了 数据库 部分 到目前为止 我们几乎已经实现了一个区块链数据库的所有元素 今天 我们将会
  • Jenkins系列:3、wsl/ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序

    Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 文章目录 Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 1 前言 2 wsl
  • beego+goAdmin+mysql+docker+natapp作为微信小程序地服务器“伪部署”

    写在前面的话 1 为什么我要叫伪部署 答 因为我把它们放在服务器运行 都是开发模式 生产模式实在不会弄 所以就这样了 2 系统环境 答 腾讯云服务器 系统为 ubuntu 版本不记得 应该是比较高的 3 前提假设 答 假设你的服务器已经安装
  • Go语言包管理(一)

    Go语言中的包 我们在使用其他语言 比如Java Python 都有类似包的概念 Go也不例外 其核心思想即为分组和模块化 人的大脑对庞大和复杂的事情很难掌控 可以对其采用分而治之的策略 使其模块化 从而更容易管理 如下是标准库中net包的
  • Go_接口、多态、接口继承、空接口、类型断言

    接口 接口是把所有具有共性的方法定义在一起 是方法集 任何类型实现了接口中所有的方法 就是实现了这个接口 接口可以实现多态 接口传递的是地址值 接口定义及调用 定义格式 tepe 接口名 interface 方法名 参数 返回值 调用格式1
  • Go 程序编译过程(基于 Go1.21)

    版本说明 Go 1 21 官方文档 Go 语言官方文档详细阐述了 Go 语言编译器的具体执行过程 Go1 21 版本可以看这个 https github com golang go tree release branch go1 21 sr
  • 48.Go简要实现令牌桶限流与熔断器并集成到Gin框架中

    文章目录 一 简介 二 限流器与熔断器在微服务中的作用 1 限流器 对某个接口单位时间内的访问量做限制 2 熔断器 当服务连续报错 超过一定阈值时 打开熔断器使得服务不可用 三 具体实现 1 限流器实现逻辑 以令牌桶算法为例 2 限流器集成
  • 【go语言开发】编写单元测试

    本文主要介绍使用go语言编写单元测试用例 首先介绍如何编写单元测试 然后介绍基本命令的使用 最后给出demo示例 文章目录 前言 命令 示例 前言 在go语言中编写单元测试时 使用说明 测试文件命名 在 Go 语言中 测试文件的命名应与被测
  • go-zero开发入门-API网关开发示例

    开发一个 API 网关 代理 https blog csdn net Aquester article details 134856271 中的 RPC 服务 网关完整源代码 file main go package main import
  • go-zero目录结构和说明

    code of conduct md 行为准则 CONTRIBUTING md 贡献指南 core 框架的核心组件 bloom 布隆过滤器 用于检测一个元素是否在一个集合中 breaker 熔断器 用于防止过多的请求导致系统崩溃 cmdli
  • go-zero开发入门之gateway深入研究1

    创建一个 gateway 示例 main go package main import flag fmt gateway middleware github com zeromicro go zero core conf github co
  • go-zero 的 etcd 配置

    实现代码在 core discov config go 文件中 type EtcdConf struct Hosts string Key string ID int64 json optional User string json opt
  • go-zero开发入门-API网关鉴权开发示例

    本文是 go zero开发入门 API网关开发示例 一文的延伸 继续之前请先阅读此文 在项目根目录下创建子目录 middleware 在此目录下创建文件 auth go 内容如下 鉴权中间件 package middleware impor
  • “go mod tidy”之错误“not a valid zip file”

    执行 go mod tidy 时 遇到如下错误 rpc imports github com zeromicro go zero zrpc imports github com zeromicro go zero zrpc resolver
  • go开发--操作mysql数据库

    在 Go 中访问 MySQL 数据库并进行读写操作通常需要使用第三方的 MySQL 驱动 Go 中常用的 MySQL 驱动有 github com go sql driver mysql 和 github com go xorm xorm
  • golang 生成一年的周数

    GetWeekTimeCycleForGBT74082005 获取星期周期 中华人民共和国国家标准 GB T 7408 2005 参数 year 年份 GB T 7408 2005 func GetWeekTimeCycleForGBT74
  • 【go语言】结构体数据填充生成md错误码文件

    这里使用pongo2这个模版引擎库进行md文件渲染 GitHub flosch pongo2 Django syntax like template engine for Go package main import fmt github

随机推荐

  • Tof,结构光,三角测距,RGBD,双目,激光雷达,毫米波雷达一文总结(一)

    Tof xff0c 结构光 xff0c 三角测距 xff0c RGBD xff0c 双目 xff0c 激光雷达 xff0c 毫米波雷达一文总结 距离测量算法解析TOF 飞行时间测距法超声波毫米波雷达激光雷达 最近在做一些无人车相关的工作 x
  • OCR-PIL.Image与Base64 String的互相转换

    1 基本环境 py2 python2 7 13py3 python3 6 2PIL pip 2 3 install pillow PIL库已不再维护 xff0c 而pillow是PIL的一个分支 xff0c 如今已超越PIL 2 Conve
  • Java中恒等条件判断:“equals”和“==”

    1 起因 xff1a 字符串恒等判断 String is reference type String str1 61 new String 34 hello 34 String str2 61 new String 34 hello 34
  • SQL小结

    1 SQL模糊查询 like 效率低 xff0c 容易全盘扫描 查找Name中包含字符 39 M 39 的数据 select ename from table where ename like 39 M 39 查找Name中第二个字母为 3
  • golang中的flag模块小结

    1 flag常用函数 无论是c语言还是golang语言或是其他语言 xff0c 启动应用程序时都可以带一些参数 xff0c 然后系统根据传入的参数进行特点的工作 如 xff1a main mode online model bert ch
  • Redis批量操作详解及性能分析

    通过mget批量执行指令可以节约网络连接和数据传输开销 xff0c 在高并发场景下可以节约大量系统资源 本文中 xff0c 我们更进一步 xff0c 比较一下redis提供的几种批量执行指令的性能 1 为什么需要批量执行redis指令 众所
  • NDCG:推荐系统/搜索评价指标

    本文转载自 胖喵 博主 xff0c 详细请看https www cnblogs com by dream p 9403984 html 1 CG xff1a 累计增益 CG xff0c cumulative gain xff0c 只考虑到了
  • 特征共线性问题

    多重共线性是使用线性回归算法时经常要面对的一个问题 在其他算法中 xff0c 例如决策树或者朴素贝叶斯 xff0c 前者的建模过程时逐渐递进 xff0c 每次都只有一个变量参与 xff0c 这种机制含有抗多重共线性干扰的功能 xff1b 后
  • 常见回归和分类损失函数比较

    文章转自知乎作者wdmad xff0c 更多内容建议阅读原文 xff1a https zhuanlan zhihu com p 36431289 本博文属于阅读笔记 xff0c 融合了个人观点 1 损失函数 损失函数的一般表示为 L y f
  • 获取keras中间层输出、模型保存与加载

    1 获取keras中间层输出 model summary and plot import keras from keras models import Model from keras utils import plot model Doc
  • HashMap底层实现和原理

    本文是在阅读知乎老刘作品后的整理 内容基于JDK1 7进行分析 xff0c 1 8做的改动文章末尾进行讲解 1 基本要义 1 1 概述 Hashmap在Map派生中的位置 HashMap基于Map接口实现 xff0c 元素以键值对的方式存储
  • 大疆激光雷达Livox Avia开箱及测试

    大疆激光雷达Livox Avia 箱子 从左至右为 xff1a 大疆激光雷达Livox Avia xff0c 电源转接插座 xff0c 内六角形L型扳手 xff0c 镜头清洁布 xff0c 螺钉包 xff0c 说明书 xff0c 1 5米航
  • Go协程与协程池

    1 Golang协程 golang和其它语言最大区别莫过于goroutine xff0c 也就是go的协程 xff0c example如下 xff1a package main import 34 fmt 34 import 34 time
  • Go协程池设计思路(Task-Job-Worker)

    1 铺垫 xff1a Go 的接收器Receiver 在go语言中 xff0c 没有类的概念 xff0c 但是可以给类型 xff08 结构体 xff0c 自定义类型 xff09 定义方法 所谓方法就是定义了接受者的函数 接受者定义在func
  • 系统间通信1:阻塞与非阻塞式通信A

    版权声明 xff1a 本文引用https yinwj blog csdn net article details 48274255 从这篇博文开始 xff0c 我们将进入一个新文章系列 这个文章系列专门整理总结了目前系统间通信的主要原理 手
  • 系统间通信1:阻塞与非阻塞式通信B

    版权声明 xff1a 本文引用https yinwj blog csdn net article details 48274255 接上篇 xff1a 系统间通信1 xff1a 阻塞与非阻塞式通信A 4 3 NIO通信框架 目前流行的NIO
  • 系统间通信2:通信管理与远程方法调用RMI

    本文引用 https yinwj blog csdn net article details 49120813 RMI Remote Method Invocation xff0c 远程方法调用 RPC Remote Procedure C
  • 系统间通信3:RPC的基本概念

    本文引用 https yinwj blog csdn net article details 49453303 1 概述 经过了详细的信息格式 网络IO模型的讲解 xff0c 并且通过JAVA RMI的讲解进行了预热 从这篇文章开始我们将进
  • 系统间通信4:基本IO通信模型

    本文引用 https blog csdn net yinwenjie article details 48472237 目前常用的IO通信模型包括四种 xff1a 阻塞式同步IO 非阻塞式同步IO 多路复用IO和真正的异步IO 所有IO模式
  • 深入理解Golang中的Context包

    context Context是Go语言中独特的设计 xff0c 在其他编程语言中我们很少见到类似的概念 context Context深度支持Golang的高并发 1 Goroutine和Channel 在理解context包之前 xff