- 如果一开始是使用
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