Featured image of post Go 和 PHP 的区别以及 Context 的参数传递

Go 和 PHP 的区别以及 Context 的参数传递

来看一下 Context 的作用

  • 如果一开始是使用PHP的朋友,写多了就会有一些疑惑
  • 两次请求先后顺序的请求该怎么产生影响.
  • 在很多语言中, 可以很轻松的实现,无非是用一个全局变量来共享
  • 但是在我们写的PHP中,不同的请求之间是完全独立的
  • 这样的设计有好处也有坏处
    • 坏:每次启动都要重新加载代码,配置
    • 好:不用考虑内存泄漏的问题,请求结束自动释放所有
  • 想要两次独立的请求产生影响,但并不是说PHP做不到, 我们也可以使用Redis之类的来达到共享内存使用

假如我们有以下代码

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

func main()  {

    router := gin.Default()
    router.GET("/ping", func(c *gin.Context) {

        go func() {

            var i int
            for {
                i ++
                fmt.Println(i)
                time.Sleep(time.Second)
            }
        }()

        time.Sleep(time.Second * 5)
        c.String(http.StatusOK, "pong")
    })
    router.Run(":8888")
}

  • 当我们访问http://127.0.0.1:8888/ping
  • 如果我们用PHP的思想,预想可能会在控制台打印 1~5, 然后浏览器响应pong,请求结束
  • 实际的结果, 控制台会不断的打印数字累加,直到我们关闭服务. 因为即可当前请求结束了(5s之后), 但是由于主进程还在, 所以这个goroutine不会被销毁,一直运行,如果大量的请求进来,那么就会导致内存泄漏.
  • 而我们想要在请求过程中打印,但是在请求结束之后立即退出,可以这样子写.
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

func main()  {

    router := gin.Default()
    router.GET("/ping", func(c *gin.Context) {

        go func() {

            var i int
            for {
                select {

                case <- c.Request.Context().Done():
                    return
                default:
                    i ++
                    fmt.Println(i)
                    time.Sleep(time.Second)
                }
            }
        }()

        time.Sleep(time.Second * 5)
        c.String(http.StatusOK, "pong")
    })
    router.Run(":8888")
}

  • 我们只要读取c.Request.Context().Done()状态即可
  • 如果请求handle还在处理任务,那么我们就不断打印
  • 当请求结束, 我们一同结束goroutine

  • 从以上就能看出,实际上我们写的PHP代码都是在goroutine handle的生命周期.
  • 如果我们想要在PHP中实现用户注册之后异步发送邮件, 那么就需要借助其它办法实现(redis 队列)
  • 但在Go中可以独立开启一个goroutine去执行任何想要的操作,并且我们也可以在这个goroutine判断该什么时候结束.

Context 参数引用问题

package main

import (
    "context"
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/go-redis/redis/v8"
    "net/http"
)

var ctx = context.Background()

func main()  {

    host := ""
    port := 0
    password := ""

    client := redis.NewClient(&redis.Options{
        Addr:     fmt.Sprintf("%s:%d", host, port),
        Password: password, // no password set
        DB:       0,        // use default DB
    })

    g := gin.Default()
    g.GET("/ping", func(c *gin.Context) {

        var msg string

        // 1
        msg = client.Get(ctx, "ping").String()

        // 2
        msg = client.Get(context.Background(), "ping").String()

        // 3
        msg = client.Get(c, "ping").String()

        // 4
        msg = client.Get(c.Request.Context(), "ping").String()

        c.String(http.StatusOK, msg)
    })
}
> 4 is the right choice.

1 is is kinda wrong because it uses one global context. Usually you want a separate context for each request.
2 creates empty context which kinda pointless.
3 uses Gin context which can't be used as a full replacement for context.Context.

https://github.com/go-redis/redis/issues/1594

引用

https://blog.golang.org/context