context
Дизайн — довольно спорная тема в Голанге.context
Это не серебряная пуля: хотя он решает некоторые проблемы, у него также есть много недостатков, которые люди критикуют. В этой статье в основном обсуждаютсяcontext
преимущества и недостатки и некоторые предложения по использованию.
недостаток
Потому что субъективно мне это тоже не очень нравится.context
дизайн, так что начнем с недостатков.
Повсюдуcontext
в соответствии сcontext
Официальная рекомендация по использованию,context
Должен появиться в первом параметре функции. Это ведет прямо к коду вездеcontext
. как вызывающая функция, даже если вы не собираетесь использоватьcontext
вам также нужно передать заполнитель -context.Background()
илиcontext.TODO()
. Это определенно запах кода, особенно для программистов с чистотой кода, передача такого количества бессмысленных параметров просто недопустима.
Err()
это очень грубо
context.Context
определяется в интерфейсеErr()
метод:
type Context interface {
...
// If Done is not yet closed, Err returns nil.
// If Done is closed, Err returns a non-nil error explaining why:
// Canceled if the context was canceled
// or DeadlineExceeded if the context's deadline passed.
// After Err returns a non-nil error, successive calls to Err return the same error.
Err() error
...
}
При срабатывании отмены (обычно это означает какие-то ошибки или аномалии),Err()
метод, чтобы увидеть причину ошибки. Это действительно общее требование, ноcontext
внутри сумкиErr()
Реализация кажется немного безвкусной,Err()
Сообщаемые сообщения об ошибках ограничиваются следующими двумя:
- Отмена из-за отмены (извините???)
- Отменено из-за тайм-аута
// Canceled is the error returned by Context.Err when the context is canceled.
var Canceled = errors.New("context canceled")
// DeadlineExceeded is the error returned by Context.Err when the context's
// deadline passes.
var DeadlineExceeded error = deadlineExceededError{}
type deadlineExceededError struct{}
func (deadlineExceededError) Error() string { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool { return true }
func (deadlineExceededError) Temporary() bool { return true }
отErr()
метод, вы вряд ли получите какие-либо сообщения об ошибках, связанных с бизнесом, то есть, если вы хотите узнать конкретную причину отмены, вы не можете ожидатьcontext
Сумка, ты должна сделать это сама. еслиcancel()
Может быть лучше, чтобы метод получил ошибку:
ctx := context.Background()
c, cancel := context.WithCancel(ctx)
err := errors.New("some error")
cancel(err) //cancel的时候能带上错误原因
context.Value
- Свобода без ограничений опасна
context.Value почтиmap[interface{}]interface{}
:
type Context interface {
...
Value(key interface{}) interface{}
...
}
Это дает программистам большой свободы, что почти хочет поставить то, что поставить. Но эта почти беспрепятственная свобода очень опасна, не только легко приведет к злоупотреблению, злоупотреблению и терять проверку типа компиляции, требует от насcontext.Value
Каждое значение в середине должно быть присвоено, чтобы предотвратитьpanic
. Хотя в документации указаноcontext.Value
Его следует использовать для сохранения данных типа «в области запроса», но для того, что «в области запроса», существует тысяча определений в глазах тысячи людей. рисунокrequest-id,access_token,user_id
Эти данные могут быть размещены как «в области запроса».context.Value
Это также может быть определено в структуре более ясным образом.
плохая читаемость
Плохая читабельность также является ценой свободы.При обучении чтению кода Go см.context
Это головная боль. Если документация не четко прокомментирована, вы вряд ли узнаетеcontext.Value
Что именно в нем содержится, не говоря уже о том, как правильно его использовать. Код нижеhttp.Request
в структуреcontext
Определение и примечания:
// http.Request
type Request struct {
....
// ctx is either the client or server context. It should only
// be modified via copying the whole Request using WithContext.
// It is unexported to prevent people from using Context wrong
// and mutating the contexts held by callers of the same request.
ctx context.Context
}
Ты видишь этоcontext.Value
Будет ли что-нибудь храниться внутри?
...
func main () {
http.Handle("/", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
fmt.Println(req.Context()) // 猜猜看这个context里面有什么?
}))
}
Я пишу тебе, я не могу не подумать о том, что «конфеты» пытал душу: эти несколько бокалов вина на столе, который является стаканом маотаи?
даже если ты будешьcontext
Вы не можете сказать, распечатываете ли вы этоcontext
Связь с входными параметрами функции, возможно, в следующий раз будет передан другой набор параметров,context
Значение внутри изменилось. Обычно это происходит, если документация непонятна (к сожалению, я обнаружил, что большая часть кода неcontext.Value
с четкими комментариями), можно искать только глобальноcontext.WithValue
, линия по линии, чтобы найти.
преимущество
Хотя я субъективенcontext
Определенная «предвзятость» есть, но объективно в ней все же есть плюсы и плюсы.
Унифицировать метод реализации отмены
Во многих статьях говоритсяcontext
решеноПроблема отмены горутины, но на самом деле я думаю реализация самой отмены не проблема, используя закрывающийchannel
Функции вещания, позволяющие отменить отмену, относительно просты, дайте каштаны:
// Cancel触发一个取消
func Cancel(c chan struct{}) {
select {
case <-c: //已经取消过了, 防止重复close
default:
close(c)
}
}
// DoSomething做一些耗时操作,可以被cancel取消。
func DoSomething(cancel chan struct{}, arg Arg) {
rs := make(chan Result)
go func() {
// do something
rs <- xxx //返回处理结果
}()
select {
case <-cancel:
log.Println("取消了")
case result := <-rs:
log.Println("处理完成")
}
}
Или вы также можете использоватьchannel
Поместите его в структуру:
type Task struct{
Arg Arg
cancel chan struct{} //取消channel
}
// NewTask 根据参数新建一个Task
func NewTask(arg Arg) *Task{
return &Task{
Arg:arg ,
cancel:make(chan struct{}),
}
}
// Cancel触发一个取消
func (t *Task) Cancel() {
select {
case <-t.c: //已经取消过了, 防止重复close
default:
close(t.c)
}
}
// DoSomething做一些耗时操作,可以被cancel取消。
func (t *Task) DoSomething() {
rs := make(chan Result)
go func() {
// do something
rs <- xxx
}()
select {
case <-t.cancel:
log.Println("取消了")
case result := <-rs:
log.Println("处理完成")
}
}
// t := NewTask(arg)
// t.DoSomething()
Видно, что реализация отмены также разнообразна. Тысяча программистов может написать тысячу реализаций. Но, к счастью, естьcontext
Реализация отмены унифицирована, иначе при каждом обращении к библиотеке приходится изучать ее механизм отмены. я думаю этоcontext
Самым большим преимуществом также является самый большой кредит. Сусликам нужно только видеть, что есть функция вcontext
, Вы знаете, как отменить выполнение функции. Если вы хотите добиться отмены, приоритет отдаетсяcontext
.
Предоставляет менее элегантный, но эффективный способ передачи по значению
context.Value
Это обоюдоострый меч, и его недостатки упомянуты выше, но при правильном использовании недостатки также могут стать преимуществами.map[interface{}]interface{}
Атрибут определяет, что он может хранить почти все.Если метод нужно отменить, он также должен иметь возможность получать любые данные, переданные вызывающей стороной, тогдаcontext.Value
Еще очень действенный способ. Как «использовать его правильно», пожалуйста, обратитесь к совету по использованию ниже.
context
Рекомендации
Рассматривать только в том случае, если требуется отменаcontext
context
Существуют две основные функции: отмена иcontext.Value
. Если вам просто нужно передавать значения между горутинами, не используйтеcontext
. Потому что в мире Гоcontext
Как правило, его можно отменить по умолчанию, и нельзя отменить.context
Звонящий легко может быть неправильно понят.
Неопределенный
context
Это не душа.
context.Value
можешь или нет
context.Value
Доступ к контенту должен быть обязанностью пользователя библиотеки.. Если это поток данных внутри самой библиотеки, пожалуйста, не используйтеcontext.Value
, поскольку эта часть данных обычно является фиксированной и контролируемой. Предположим, что модулю аутентификации в системе нужна строкаtoken
Для аутентификации, сравнивая следующие две реализации, очевидно, что дисплей будетtoken
Проще передать как параметр.
// 用context
func IsAdminUser(ctx context.Context) bool {
x := token.GetToken(ctx)
userObject := auth.AuthenticateToken(x)
return userObject.IsAdmin() || userObject.IsRoot()
}
// 不用context
func IsAdminUser(token string, authService AuthService) int {
userObject := authService.AuthenticateToken(token)
return userObject.IsAdmin() || userObject.IsRoot()
}
Пример исходного кода:How to correctly use context.Context in Go 1.7
Итак, забудьте о «области запроса», поставьтеcontext.Value
Думайте об этом как о "пользовательской области" - пусть пользователь, вызывающий библиотекуcontext.Value
что туда положить.
использоватьNewContext
иFromContext
Доступcontext
Не используйте напрямуюcontext.WithValue()
иcontext.Value("key")
для доступа к данным,context.Value
Уровень инкапсуляции может эффективно уменьшить избыточность кода, улучшить его читаемость и максимально предотвратить некоторые ошибки по невнимательности.context.Context
Аннотации в интерфейсе дают нам хороший пример:
package user
import "context"
// User is the type of value stored in the Contexts.
type User struct {...}
// key is an unexported type for keys defined in this package.
// This prevents collisions with keys defined in other packages.
type key int
// userKey is the key for user.User values in Contexts. It is
// unexported; clients use user.NewContext and user.FromContext
// instead of using this key directly.
var userKey key
// NewContext returns a new Context that carries value u.
func NewContext(ctx context.Context, u *User) context.Context {
return context.WithValue(ctx, userKey, u)
}
// FromContext returns the User value stored in ctx, if any.
func FromContext(ctx context.Context) (*User, bool) {
u, ok := ctx.Value(userKey).(*User)
return u, ok
}
При использованииcontext.Value
, пожалуйста, прокомментируйте ясно
упомянутый выше,context.Value
Читабельность очень плохая, поэтому приходится компенсировать это документацией и комментариями. хоть перечисли все что можноcontext.Value
и их методы установки (NewContext(),FromContext()
), перечислите как можно больше функцийcontext.Value
Отношения между вами и людьми, которые читают или поддерживают ваш код, стали более заботливыми.
пакет для уменьшенияcontext.TODO()
илиcontext.Background()
для тех, кто предоставляетcontext
метод, но как вызывающая сторона мы его не используем, нам все равно нужно передатьcontext.TODO()
илиcontext.Background()
. Если ты не выносишь много бесполезногоcontext
Распространение кода, слой инкапсуляции можно выполнить следующими методами:
// 假设有如下查询方法,但我们几乎不使用其提供的context
func QueryContext(ctx context.Context, query string, args []NamedValue) (Rows, error) {
...
}
// 封装一下
func Query(query string, args []NamedValue) (Rows, error) {
return QueryContext(context.Background(), query, args)
}