文章目录
0.实战场景题
使用golang实现一款自带过期自动释放的单机锁
(1)过期自动释放
(2)解锁身份校验
(3) context+goroutinue
1.前言
Golang context是Golang应用开发常用的并发控制技术,它与WaitGroup最大的不同点是context对于派生 goroutine有更强的控制力,它可以控制多级的goroutine。 context翻译成中文是”上下文”,即它可以控制一组呈树状结构的goroutine,每个goroutine拥有相同的上下文。
context实际上只定义了接口,凡是实现该接口的类都可称为是一种context,官方包中实现了几个常用的 context,分别可用于不同的场景。
2.数据结构
1.接口
type Context interface {
Deadline() (deadline time.Time, ok bool)//返会ctx的过期时间
Done() <-chan struct{}//返回context中的chan
Err() error
Value(key any) any//存放
}
- deadline:返回ctx的过期时间
- done:返回标识ctx是否结束的chan
- value:返回存放对应的key的value,如果value值符合协议的标识,返回context自身
2.empty_context
type emptyCtx struct{}
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 any) any {
return nil
}
3.context.background和context.todo
Background 返回一个非 nil、空的 [Context]。它永远不会被取消,没有值,也没有截止日期。它通常由 main 函数、初始化和测试使用,并用作传入请求的顶级 Context。
type backgroundCtx struct{ emptyCtx }
TODO 返回一个非 nil、空的 [Context]。代码应使用上下文。当不清楚使用哪个 Context 或尚不可用时,请执行 TODO(因为周围函数尚未扩展以接受 Context 参数)。
type todoCtx struct{ emptyCtx }
- context.Background():
- 这是一个根上下文,没有截止日期,没有值,也没有被取消的能力。
- 通常用作最顶层的 context,作为其他 context 的父节点。
- context.TODO():
- 这个函数返回一个未经初始化的 context。
- 当你不确定应该使用哪种 context 时,可以暂时使用这个。
context包提供了4个方法创建不同类型的context,使用这四个方法时如果没有父context,都需要传入backgroud,即backgroud作为其父节点: - WithCancel()
- WithDeadline()
- WithTimeout()
- WithValue()
context包中实现Context接口的struct,除了emptyCtx外,还有cancelCtx、timerCtx和valueCtx三种,正是基于这三种context实例,实现了上述4种类型的context。
3.三种context类型
我们经常会使用到context.WithCancel()、context.WithDeadline()、context.WithTimeout()、context.WithValue()
;现在基于源码认识一下这三种Ctx,以便能学习其特性。
1.cancelCtx
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done atomic.Value//懒加载
children map[canceler]struct{}//set记录当前节点的孩子
err error//ctx关闭后不为空
cause error//ctx关闭后不为空
}
type canceler interface {
cancel(removeFromParent bool, err, cause error)
Done() <-chan struct{}
}
此context被cancle时会把其中的所有child都cancle掉。cancelCtx与deadline和value无关,所以只需要实现Done()和Err()接口外露接口即可。
func (c *cancelCtx) Done() <-chan struct{} {
d := c.done.Load()
if d != nil {
return d.(chan struct{})
}
c.mu.Lock()
defer c.mu.Unlock()
d = c.done.Load()
if d == nil {
d = make(chan struct{})
c.done.Store(d)
}
return d.(chan struct{})
}
func (c *cancelCtx) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}
构造
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := withCancel(parent)
return c, func() { c.cancel(true, Canceled, nil) }//关闭管道的函数
}
安排在父级取消时取消子级。
它设置cancelCtx的父上下文。
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {
c.Context = parent
done := parent.Done()
if done == nil {
return // parent is never canceled
}
select {
case <-done:
// parent is already canceled
child.cancel(false, parent.Err(), Cause(parent))
return
default:
}
检查父ctx是不是canCtx,如果是,需要把孩子节点添加到parrent的孩子set中
if p, ok := parentCancelCtx(parent); ok {
// parent is a *cancelCtx, or derives from one.
p.mu.Lock()
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err, p.cause)
} else {
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
return
}
if a, ok := parent.(afterFuncer); ok {
// parent implements an AfterFunc method.
c.mu.Lock()
stop := a.AfterFunc(func() {
child.cancel(false, parent.Err(), Cause(parent))
})
c.Context = stopCtx{
Context: parent,
stop: stop,
}
c.mu.Unlock()
return
}
goroutines.Add(1)
启动子协程等待父亲结束
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err(), Cause(parent))
case <-child.Done():
}
}()
}
2.timeCtx
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
timerCtx在cancelCtx基础上增加了deadline用于标示自动cancel的最终时间,而timer就是一个触发自动
cancel的定时器。
由此,衍生出WithDeadline()和WithTimeout()。实现上这两种类型实现原理一样,只不过使用语境不一样:
- deadline: 指定最后期限,比如context将2018.10.20 00:00:00之时自动结束
- timeout: 指定最长存活时间,比如context将在30s后结束。
对于接口来说,timerCtx在cancelCtx基础上还需要实现Deadline()和cancel()方法,其中cancel()方法是重写的。
Deadline()方法仅仅是返回timerCtx.deadline而已。而timerCtx.deadline是WithDeadline()或
WithTimeout()方法设置的。
cancel()方法基本继承cancelCtx,只需要额外把timer关闭。
timerCtx被关闭后,timerCtx.cancelCtx.err将会存储关闭原因:
- 如果deadline到来之前手动关闭,则关闭原因与cancelCtx显示一致;
- 如果deadline到来时自动关闭,则原因为:”context deadline exceeded”
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
deadline: d,
}
c.cancelCtx.propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded, cause) // deadline has already passed
return c, func() { c.cancel(false, Canceled, nil) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
//定时触发关闭
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded, cause)
})
}
return c, func() { c.cancel(true, Canceled, nil) }
}
3.valueCtx
type valueCtx struct {
Context
key, val any
}
设置value
func WithValue(parent Context, key, val any) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
查找value,直到根节点或者找到返回
func value(c Context, key any) any {
for {
switch ctx := c.(type) {
case *valueCtx:
if key == ctx.key {
return ctx.val
}
c = ctx.Context
case *cancelCtx:
if key == &cancelCtxKey {
return c
}
c = ctx.Context
case withoutCancelCtx:
if key == &cancelCtxKey {
// This implements Cause(ctx) == nil
// when ctx is created using WithoutCancel.
return nil
}
c = ctx.c
case *timerCtx:
if key == &cancelCtxKey {
return &ctx.cancelCtx
}
c = ctx.Context
case backgroundCtx, todoCtx:
return nil
default:
return c.Value(key)
}
}
}