context简介
包context定义了Context类型,该类型在API边界之间以及进程之间传递截止日期,取消信号和其他请求范围的值。
对服务器的传入请求应创建一个上下文,而对服务器的传出调用应接受一个上下文。它们之间的函数调用链必须传播Context,可以选择将其替换为使用WithCancel,WithDeadline,WithTimeout或WithValue创建的派生Context。 取消上下文后,从该上下文派生的所有上下文也会被取消。
WithCancel,WithDeadline和WithTimeout函数采用Context(父级)并返回派生的Context(子级)和CancelFunc。 调用CancelFunc会取消该子代及其子代,删除父代对该子代的引用,并停止所有关联的计时器。未能调用CancelFunc会使子代及其子代泄漏,直到父代被取消或计时器触发。 go vet tool 检查所有控制流路径上是否都使用了CancelFuncs。
使用上下文的程序应遵循以下规则,以使各个包之间的接口保持一致,并启用静态分析工具来检查上下文传播:
- 不要将上下文存储在结构类型中; 而是将上下文明确传递给需要它的每个函数。 Context应该是第一个参数,通常命名为ctx:
- 即使函数允许,也不要传递nil Context。 如果不确定使用哪个上下文,请传递context.TODO。
- 仅将上下文值用于传递流程和API的请求范围的数据,而不用于将可选参数传递给函数。
可以将相同的上下文传递给在不同goroutine中运行的函数。 上下文对于由多个goroutine同时使用是安全的。
// A Context carries a deadline, cancelation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {
// Done returns a channel that is closed when this Context is canceled
// or times out.
Done() <-chan struct{}
// Err indicates why this context was canceled, after the Done channel
// is closed.
Err() error
// Deadline returns the time when this Context will be canceled, if any.
Deadline() (deadline time.Time, ok bool)
// Value returns the value associated with key or nil if none.
Value(key interface{}) interface{}
}
Done方法返回一个管道,作为代表上下文运行的函数的取消信号:当通道关闭时,函数应该放弃它们的工作并返回。Err方法返回一个错误,指示为什么取消上下文。
一个Context并没有Cancel方法,因为Done管道是一个只能接受的管道。函数接受一个取消信号。特别地,当一个父操作启动goroutine为了子操作, 这些子操作不应该有能力取消父操作。取代的,WithCancel函数提供了一个方式取消一个新的Context.
Deadline方法允许函数决定他们是否应该启动工作。如果时间较短,就不值得。 Value方法允许一个Context传递请求域的数据,数据必须是多goroutine安全的。
派生context
上下文包提供了从现有值派生新的Context值的功能。这些值形成一棵树:取消上下文时,从该上下文派生的所有上下文也会被取消。
context.Background()
是任何上下文树的根; 它永远不会被取消
WithCancel和WithTimeout返回派生的Context值,该值可以比父Context早被取消。请求处理程序返回时,通常会取消与传入请求关联的上下文。使用多个副本时,WithCancel对于取消冗余请求也很有用。WithTimeout对于设置对后端服务器的请求的截止日期很有用:
// WithCancel returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed or cancel is called.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
// A CancelFunc cancels a Context.
type CancelFunc func()
// WithTimeout returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed, cancel is called, or timeout elapses. The new
// Context's Deadline is the sooner of now+timeout and the parent's deadline, if
// any. If the timer is still running, the cancel function releases its
// resources.
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithValue提供了一种将请求范围的值与Context相关联的方法:
// WithValue returns a copy of parent whose Value method returns val for key.
func WithValue(parent Context, key interface{}, val interface{}) Context
源码中context的使用案例
根据他database/sql
源码中的Ping()
方法来学习一下context.Context
的使用。
首先,Ping()
方法的原型如下:
// Ping verifies a connection to the database is still alive,
// establishing a connection if necessary.
func (db *DB) Ping() error {
return db.PingContext(context.Background())
}
PingContext
接受一个上下文作为参数,进行验证一个数据库连接是否还在存活。如果需要建立一个新的连接
// PingContext verifies a connection to the database is still alive,
// establishing a connection if necessary.
func (db *DB) PingContext(ctx context.Context) error {
var dc *driverConn
var err error
for i := 0; i < maxBadConnRetries; i++ {
dc, err = db.conn(ctx, cachedOrNewConn)
if err != driver.ErrBadConn {
break
}
}
if err == driver.ErrBadConn {
dc, err = db.conn(ctx, alwaysNewConn)
}
if err != nil {
return err
}
return db.pingDC(ctx, dc, dc.releaseConn)
}
conn
返回一个新的连接或者缓存的连接。如果连接复用策略是cachedOrNewConn
,那么如果在空闲连接够用的情况下,会使用空闲连接列表中第一个连接作为返回值返回。
// conn returns a newly-opened or cached *driverConn.
func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error) {
db.mu.Lock()
if db.closed {
db.mu.Unlock()
return nil, errDBClosed
}
// Check if the context is expired.
select {
default:
case <-ctx.Done():
db.mu.Unlock()
return nil, ctx.Err()
}
lifetime := db.maxLifetime
// Prefer a free connection, if possible.
numFree := len(db.freeConn)
if strategy == cachedOrNewConn && numFree > 0 {
conn := db.freeConn[0]
copy(db.freeConn, db.freeConn[1:])
db.freeConn = db.freeConn[:numFree-1]
conn.inUse = true
db.mu.Unlock()
if conn.expired(lifetime) {
conn.Close()
return nil, driver.ErrBadConn
}
// Lock around reading lastErr to ensure the session resetter finished.
conn.Lock()
err := conn.lastErr
conn.Unlock()
if err == driver.ErrBadConn {
conn.Close()
return nil, driver.ErrBadConn
}
return conn, nil
}
// Out of free connections or we were asked not to use one. If we're not
// allowed to open any more connections, make a request and wait.
if db.maxOpen > 0 && db.numOpen >= db.maxOpen {
// Make the connRequest channel. It's buffered so that the
// connectionOpener doesn't block while waiting for the req to be read.
req := make(chan connRequest, 1)
reqKey := db.nextRequestKeyLocked()
db.connRequests[reqKey] = req
db.waitCount++
db.mu.Unlock()
waitStart := time.Now()
// Timeout the connection request with the context.
select {
case <-ctx.Done():
// Remove the connection request and ensure no value has been sent
// on it after removing.
db.mu.Lock()
delete(db.connRequests, reqKey)
db.mu.Unlock()
atomic.AddInt64(&db.waitDuration, int64(time.Since(waitStart)))
select {
default:
case ret, ok := <-req:
if ok && ret.conn != nil {
db.putConn(ret.conn, ret.err, false)
}
}
return nil, ctx.Err()
case ret, ok := <-req:
atomic.AddInt64(&db.waitDuration, int64(time.Since(waitStart)))
if !ok {
return nil, errDBClosed
}
if ret.err == nil && ret.conn.expired(lifetime) {
ret.conn.Close()
return nil, driver.ErrBadConn
}
if ret.conn == nil {
return nil, ret.err
}
// Lock around reading lastErr to ensure the session resetter finished.
ret.conn.Lock()
err := ret.conn.lastErr
ret.conn.Unlock()
if err == driver.ErrBadConn {
ret.conn.Close()
return nil, driver.ErrBadConn
}
return ret.conn, ret.err
}
}
db.numOpen++ // optimistically
db.mu.Unlock()
ci, err := db.connector.Connect(ctx)
if err != nil {
db.mu.Lock()
db.numOpen-- // correct for earlier optimism
db.maybeOpenNewConnections()
db.mu.Unlock()
return nil, err
}
db.mu.Lock()
dc := &driverConn{
db: db,
createdAt: nowFunc(),
ci: ci,
inUse: true,
}
db.addDepLocked(dc, dc)
db.mu.Unlock()
return dc, nil
}
现在PingContext
获取到一个连接之后就要进行真正的pingDC
操作了,该函数的第一个参数就是上下文参数,符合上下文使用的规范。 第三个参数是一个函数类型,先来看看pingDC
操作,稍后再具体看看这个释放功能的函数。pingDC
函数,从驱动连接中获取Pinger
。 然后调用释放函数。
func (db *DB) pingDC(ctx context.Context, dc *driverConn, release func(error)) error {
var err error
if pinger, ok := dc.ci.(driver.Pinger); ok {
withLock(dc, func() {
err = pinger.Ping(ctx)
})
}
release(err)
return err
}
看一下释放函数
func (dc *driverConn) releaseConn(err error) {
dc.db.putConn(dc, err, true)
}
因为重点不在该函数,所以简单描述一下这个函数的功能就是将连接放回到空闲连接中,供后续的数据库连接使用。
具体看看PingDC
中真正执行ping
操作的逻辑
withLock是一个闭包,在核心函数调用上加锁。PingDC函数中使用的就是第一个参数结构体上的锁。来保护第二个参数,一个函数类型的参数调用。该函数进行具体的ping操作。
Pinger
是一个接口类型,只有一个Ping
方法,该方法仅有一个参数,就是上下文参数。而这个接口类型是一个可选的接口由Conn
实现。 如果Conn
并没有实现Pinger
,sql
包中的DB.Ping和DB.PingContext
将检测是否有一个Conn可用。
// Pinger is an optional interface that may be implemented by a Conn.
//
// If a Conn does not implement Pinger, the sql package's DB.Ping and
// DB.PingContext will check if there is at least one Conn available.
//
// If Conn.Ping returns ErrBadConn, DB.Ping and DB.PingContext will remove
// the Conn from pool.
type Pinger interface {
Ping(ctx context.Context) error
}
如果Conn.Ping
返回ErrBadConn
,DB.Ping
和DB.PingContext
将移除连接池中的Conn
.直到检测到是否有一个Conn可用。
看完了数据库中的Ping
功能,感觉是不是和你想象中的不太一样啊,我也是….