Context接口的设计

huangapple go评论65阅读模式
英文:

Design of Context interface

问题

我的问题是关于Context接口的设计选择。如果我想从一个parent创建一个child上下文,我可以这样做:

child, cancel := context.WithTimeout(parent, timeout)

如果WithTimeout是接口的一部分,我们可以简单地写成:

child, cancel := parent.WithTimeout(timeout)

这样看起来更加清晰。它更短,而且不需要import context

为什么生成子上下文的函数不是Context接口的一部分呢?

英文:

My question is about a design choice for the Context interface. If I want to create a child context from a parent I can do something like:

child, cancel := context.WithTimeout(parent, timeout)

Wouldn't it be nicer if WithTimeout was part of the interface, so that we could simply write:

child, cancel := parent.WithTimeout(timeout)

It seems so much cleaner to me. It is shorter, and there is no need to import context.

Why are the functions to produce child contexts not part of the Context interface?

答案1

得分: 6

这是context.Context类型:

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

很简单。如果你要编写它的实现,你能做到吗?是的,很容易。因为没有"setter"方法,每个方法只需返回一个默认值或零值,这就是一个"有效"的实现。这正是背景和TODO上下文所做的(context.Background()context.TODO())。

如果你要在Context接口本身中添加从现有上下文"派生"新上下文的函数(例如context.WithCancel()context.WithDeadline()等),那就需要为所有函数提供一个(有效的)实现,这是不可行的;而且通常不需要同时使用所有这些函数,因此这将是一种资源浪费。

扩展是负责添加这些实现的。如果你查看context包的实现:context/context.go,你会看到不同的context.Context实现用于不同的"派生"或"扩展":

// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int

// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
    Context

    done chan struct{} // closed by the first cancel call.

    mu       sync.Mutex
    children map[canceler]bool // set to nil by the first cancel call
    err      error             // set to non-nil by the first cancel call
}

// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
    cancelCtx
    timer *time.Timer // Under cancelCtx.mu.

    deadline time.Time
}

// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
    Context
    key, val interface{}
}

显然,我们可以为context.Context创造其他有用的扩展,这些扩展不在context包中。如果你有一个新的想法,你会将它添加到Context接口中吗?这将破坏所有现有的实现,因为显然你的新想法在其他人当前的实现中没有被实现。

英文:

This is the context.Context type:

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() &lt;-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

It's simple. If you were to write an implementation of it, could you do it? Yes, easily. As there are no "setter" methods, each method can just return a default / zero value, and it's a "valid" implementation. That's exactly what the background and TODO contexts do (context.Background() and context.TODO()).

If you would add the functions that derive new context from an existing one (e.g. context.WithCancel(), context.WithDeadline() etc.) as part of the Context interface itself, that would require to provide a (valid) implementation for all, which is not feasible; and also all of them at the same time is rarely needed, so it would be a waste of resources.

Extensions are responsible to add the implementations. If you look at how the context package is implemented: context/context.go, you'll see different context.Context implementations for the different "derivatives" or "extensions":

// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int


// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
	Context

	done chan struct{} // closed by the first cancel call.

	mu       sync.Mutex
	children map[canceler]bool // set to nil by the first cancel call
	err      error             // set to non-nil by the first cancel call
}

// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
	Context
	key, val interface{}
}

Obviously we can make up other useful extensions for context.Context which are not in the context package. If you have a new idea, would you also add that to the Context interface? That would break all existing implementations, as obviously your new idea is not implemented in others' current implementations.

答案2

得分: 1

我认为有两个原因:

  1. 这是因为WithContextparent没有任何关系 - 例如,父级不应该需要也不应该知道你可以从它创建一个子上下文的事实。在Go的理念中,接口应该尽可能简化。

  2. 这样更易读和清晰地了解输出结果。在当前的实现中,someVar, _ := context.WithTimeout(value)被读作some variable is a new (:=) context with a timeout。在你提出的版本中,它是someVar, _ := parent.WithTimeout(value),这有点更加隐晦。

英文:

IMHO there are 2 reasons:

  1. It is because WithContext has nothing to do with the parent - e.g. parent should not need and should not have any ideas about the fact you can create a child context from it. In Go ideology interface should be as minimal as possible.

  2. It is more readable and clear what are you getting as output. In the current implementation someVar, _ := context.WithTimeout(value) is readed as some variable is a new (:=) context with a timeout. In your suggested version it's someVar, _ := parent.WithTimeout(value) it's a bit more obscure.

huangapple
  • 本文由 发表于 2016年11月17日 20:13:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/40654605.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定