为每个 goroutine 拥有自己的储存空间,供不同的情境识别与使用
举例来说,如果正在设计一个 logger 物件,我们希望不同 goroutine 透过此物件纪录 log 时也能说明此动作是哪个 goroutine 做的,那此 logger 物件就需要「能辨识不同的 goroutine」。
以 java 来说,设计的方式是一个 logger 物件拥有 ThreadLocal 类别,ThreadLocal 简单来说即是一个 hashmap,但他在.get()
与.set()
时都可以以 thread id 来当作 key,并存入任意 value。
不同的 thread 从此空间拿取资料只能依照自己的 thread id 拿取,所以不会产生 race condition,也不需要 lock 保护,如图:
golang 的标准库不提供取得 goroutine id 的方法,gopher Andrew Gerrand 认为:
We wouldn't even be having this discussion if thread local storage wasn't useful. But every feature comes at a cost, and in my opinion the cost of threadlocals far outweighs their benefits. They're just not a good fit for Go.
即取得 id 的开销比实际的应用来的大,所以不采用此方式。虽然有gls这样的 library,但是利用 stack 的特性来实作,并不是很稳定的作法。
所以 golang 是采用 context 的做法来达到「让 goroutine 拥有自己的储存空间」,context 可以利用With
等方法,把 context 夹带某些值,带到不同的 goroutine,
context.WithValue(ctx, key, val)
: 可以将原有 context 新增某 key,并存放着某 value除此之外也有其他With
方法,让 context 可以夹带某些功能,
context.WithCancel(ctx, cancelFunc)
: 让 context 夹带取消功能,当呼叫取消後,context.Done()
就会发出 close 讯号context.WithDeadline(ctx, deadlineTime)
: 让 context 夹带特定时间结束功能,当到特定时间後,context.Done()
就会发出 close 讯号context.WithTimeout(ctx, timeout)
: 让 context 夹带倒数计时结束功能,当倒数计时完毕後,context.Done()
就会发出 close 讯号每个 goroutine 都可以用的context.With
来复制一份 context 并添加事物,藉此达到独立的储存空间,如图:
上图每个地鼠都是一个 goroutine,他们都拥有自己透过With
复制的 context
设计一个 server 的 logger 系统,在不同 goroutine request 进入系统後,log 时都会显示 request id
相关的 code 在Github - go-design-patterns。
透过context.WithValue()
,将不同 goroutine request 的 context 夹带唯一的 uuid,在RequestHandler()
运行时就可以用ctx.Value(RequestID{})
取出 request id 来 log,如下:
package main
import (
"context"
"fmt"
"time"
uuid "github.com/satori/go.uuid"
)
type RequestID struct{}
func RequestHandler(ctx context.Context) {
fmt.Printf("request ID is %s\n", ctx.Value(RequestID{}))
// do something
}
func main() {
ctx := context.Background()
go RequestHandler(context.WithValue(ctx, RequestID{}, uuid.NewV4().String()))
go RequestHandler(context.WithValue(ctx, RequestID{}, uuid.NewV4().String()))
go RequestHandler(context.WithValue(ctx, RequestID{}, uuid.NewV4().String()))
time.Sleep(10 * time.Second) //等待goroutine执行完毕
}
事实上这就是很多框架如gin, iris对 request id 添加方式,会在 router 设定处添加RequestIDMiddleware()
这类 middleware,并会在里头处理与context.WithValue()
概念相同的实作,将 request id 添加到此 request 的 context 上,供後续 log 使用,gin 的范例如下:
func RequestIdMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("X-Request-Id", uuid.NewV4().String())
c.Next()
}
}
func main() {
router := gin.Default()
router.Use(RequestIdMiddleware)
// 其他router设定
}
>>: [DAY 09] ASG (Auto Scaling Group)
前言 这是一个七年 Android 工程师专为麻瓜写的。 麻瓜是指: 不会魔法、选错科系入错行、被老...
关於讯息伫列怎麽去储存呢?大致分成下列两种: 1.系统池(system pool):如果能确定讯息伫...
今天是实作 React.js 网站的最後一篇介绍了,这篇会大量的使用到 React Hooks 的...
Machine learning常用到的一个diabetes 糖尿病资料集,Pima印第安人数据起源...
温馨小提醒:吃多少,拿多少(避免造成浪费啊~) 疫情前,我总是会在影片下方注明「吃多少,拿多少(避免...