In the Server Go http packet, each request has a corresponding goroutine to handle. Request handler usually starts additional goroutine used to access back-end services, such as database and RPC services. For processing a request goroutine typically involve accessing the requested specific data, such as end-user identity authentication information, authentication token associated, the request deadline. When a request is canceled or expires, all to handle the request goroutine should quickly withdraw, then the system can release these goroutine occupied resources.
Why Context
A basic example
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
// 初始的例子
func worker() {
for {
fmt.Println("worker")
time.Sleep(time.Second)
}
// 如何接收外部命令实现退出
wg.Done()
}
func main() {
wg.Add(1)
go worker()
// 如何优雅的实现结束子goroutine
wg.Wait()
fmt.Println("over")
}
Global Variables way
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
var exit bool
// 全局变量方式存在的问题:
// 1. 使用全局变量在跨包调用时不容易统一
// 2. 如果worker中再启动goroutine,就不太好控制了。
func worker() {
for {
fmt.Println("worker")
time.Sleep(time.Second)
if exit {
break
}
}
wg.Done()
}
func main() {
wg.Add(1)
go worker()
time.Sleep(time.Second * 3) // sleep3秒以免程序过快退出
exit = true // 修改全局变量实现子goroutine的退出
wg.Wait()
fmt.Println("over")
}
Channel mode
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
// 管道方式存在的问题:
// 1. 使用全局变量在跨包调用时不容易实现规范和统一,需要维护一个共用的channel
func worker(exitChan chan struct{}) {
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Second)
select {
case <-exitChan: // 等待接收上级通知
break LOOP
default:
}
}
wg.Done()
}
func main() {
var exitChan = make(chan struct{})
wg.Add(1)
go worker(exitChan)
time.Sleep(time.Second * 3) // sleep3秒以免程序过快退出
exitChan <- struct{}{} // 给子goroutine发送退出信号
close(exitChan)
wg.Wait()
fmt.Println("over")
}
The official version of the program
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func worker(ctx context.Context) {
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Second)
select {
case <-ctx.Done(): // 等待上级通知
break LOOP
default:
}
}
wg.Done()
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 3)
cancel() // 通知子goroutine结束
wg.Wait()
fmt.Println("over")
}
When the sub goroutine had opened another goroutine, only you need to pass ctx:
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func worker(ctx context.Context) {
go worker2(ctx)
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Second)
select {
case <-ctx.Done(): // 等待上级通知
break LOOP
default:
}
}
wg.Done()
}
func worker2(ctx context.Context) {
LOOP:
for {
fmt.Println("worker2")
time.Sleep(time.Second)
select {
case <-ctx.Done(): // 等待上级通知
break LOOP
default:
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 3)
cancel() // 通知子goroutine结束
wg.Wait()
fmt.Println("over")
}
Context acquaintance
Go1.7 adding a new standard library context
, which defines the Context
type, designed to simplify the process for data requests between a plurality of individual goroutine domain request, the cancel signal, the deadline, and other related operations, which may involve multiple an API call.
Incoming requests to the server should create a context, and outgoing calls to the server should accept context. Function call chain context must be passed between them, or you can use WithCancel
, WithDeadline
, WithTimeout
or WithValue
create derivative context. When a context is canceled, all derived context it was canceled.
Context interface
context.Context
Is an interface, the interface defines four methods need to be implemented. Specific signature as follows:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
among them:
Deadline
The method needs to return the currentContext
time to be canceled, which is the deadline for the completion of the work (deadline);Done
The method requires a returnChannel
, the Channel will be closed after the current work is completed or canceled context, repeatedly invokedDone
method returns the same Channel;Err
The method returns the current
Context
The reason the end, it will only
Done
Channel will return a non-null value returned is closed;
- If the current
Context
is canceled it will returnCanceled
an error; - If the current
Context
time-out will returnDeadlineExceeded
an error;
- If the current
Value
The method willContext
return the value corresponding to the key, for a same context, the multiple callsValue
and pass the sameKey
will return the same result, only the method for communicating data across the API with the request field and inter-process;
Background()和TODO()
Go two built-in functions: Background()
and TODO()
these two functions return an implementation of the Context
interface background
and todo
. We are beginning to code two built-in context object as the topmost partent context
, derived more sub-context object.
Background()
Mainly used for the main function, initialization and test code, as the topmost Context Context of this tree, i.e. the root Context.
TODO()
It is not yet know the specific usage scenarios, if we do not know when to use what Context, you can use this.
background
And todo
are essentially emptyCtx
structure type, is a non-cancelable, deadline is not set, it does not carry any value Context.
With family of functions
In addition, context
the package also defines a series of four With function.
WithCancel
WithCancel
The function signature is as follows:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel
Returns a copy of the parent node Done with the new channel. When the call to return to cancel the function or when the context Done to close the parent channel, the channel Done Close to return to the context, no matter what occurs first.
Cancel this context will release its associated resources, so the operation should run the code in this context calls cancel immediately upon completion.
func gen(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
return // return结束该goroutine,防止泄露
case dst <- n:
n++
}
}
}()
return dst
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 当我们取完需要的整数后调用cancel
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
}
Sample code above, the gen
function generates and sends them to the integer in a separate return channel in goroutine. gen caller after use integer generated need to cancel context, so as not to gen
start internal goroutine leak.
WithDeadline
WithDeadline
The function signature is as follows:
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
Returns a copy of the parent context, and adjust the deadline later than d. If the deadline has earlier parent context in d, the WithDeadline (parent, d) semantically equivalent to the parent context. When the deadline expires, when the cancel function call returns, or when the parent context Done passage is closed, returns the context of the Done passage is closed, whichever occurs first.
Cancel this context will release its associated resources, so the operation should run the code in this context calls cancel immediately upon completion.
func main() {
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
// 尽管ctx会过期,但在任何情况下调用它的cancel函数都是很好的实践。
// 如果不这样做,可能会使上下文及其父类存活的时间超过必要的时间。
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
The above code defines a 50 ms after the expiration of the deadline, then we call context.WithDeadline(context.Background(), d)
to get a context (ctx) and a cancel function (cancel), and then use a select make the main program into a wait: Wait one second printing overslept
Exit or wait ctx quit expired. Because ctx50 seconds after the expiration date, it ctx.Done()
will first receives the value, the above code will print ctx.Err () The reason for cancellation.
WithTimeout
WithTimeout
The function signature is as follows:
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout
Return WithDeadline(parent, time.Now().Add(timeout))
.
Cancel this context associated with the release of resources, and therefore, run code should in this context cancel call immediately after completion, typically for a timeout control database or network connection. Specific examples are as follows:
package main
import (
"context"
"fmt"
"sync"
"time"
)
// context.WithTimeout
var wg sync.WaitGroup
func worker(ctx context.Context) {
LOOP:
for {
fmt.Println("db connecting ...")
time.Sleep(time.Millisecond * 10) // 假设正常连接数据库耗时10毫秒
select {
case <-ctx.Done(): // 50毫秒后自动调用
break LOOP
default:
}
}
fmt.Println("worker done!")
wg.Done()
}
func main() {
// 设置一个50毫秒的超时
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 5)
cancel() // 通知子goroutine结束
wg.Wait()
fmt.Println("over")
}
WithValue
WithValue
Function can be used to request data scope relationship with Context object. The following statement:
func WithValue(parent Context, key, val interface{}) Context
WithValue
Returns a copy of the parent node, which is associated with key val.
Data transfer between processes and only API request field using context values, rather than using it to pass optional arguments to the function.
Keys provided must be comparable, and should not be string
type or any other built-in type, in order to avoid the use of a conflict occurs between the packet context. WithValue
The user should define their own key type. To avoid partitioned allocated to the interface {}, usually having a particular type of context key struct{}
. Or, the static type derived context crucial variable should be a pointer or interface.
package main
import (
"context"
"fmt"
"sync"
"time"
)
// context.WithValue
type TraceCode string
var wg sync.WaitGroup
func worker(ctx context.Context) {
key := TraceCode("TRACE_CODE")
traceCode, ok := ctx.Value(key).(string) // 在子goroutine中获取trace code
if !ok {
fmt.Println("invalid trace code")
}
LOOP:
for {
fmt.Printf("worker, trace code:%s\n", traceCode)
time.Sleep(time.Millisecond * 10) // 假设正常连接数据库耗时10毫秒
select {
case <-ctx.Done(): // 50毫秒后自动调用
break LOOP
default:
}
}
fmt.Println("worker done!")
wg.Done()
}
func main() {
// 设置一个50毫秒的超时
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
// 在系统的入口中设置trace code传递给后续启动的goroutine实现日志数据聚合
ctx = context.WithValue(ctx, TraceCode("TRACE_CODE"), "12512312234")
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 5)
cancel() // 通知子goroutine结束
wg.Wait()
fmt.Println("over")
}
Note the use of Context
- Recommended to pass parameters displayed Context
- Context function as a parameter to the method, should Context as the first parameter.
- When passed to a function method Context, do not pass nil, if you do not know what passed, use context.TODO ()
- Value Context related data necessary to deliver the requested domain method should not be used to pass optional parameters
- Context is thread-safe, you can rest assured of passing the multiple goroutine
Client timeout cancel examples
How to control the timeout when the client calls the server-side API?
end server
// context_timeout/server/main.go
package main
import (
"fmt"
"math/rand"
"net/http"
"time"
)
// server端,随机出现慢响应
func indexHandler(w http.ResponseWriter, r *http.Request) {
number := rand.Intn(2)
if number == 0 {
time.Sleep(time.Second * 10) // 耗时10秒的慢响应
fmt.Fprintf(w, "slow response")
return
}
fmt.Fprint(w, "quick response")
}
func main() {
http.HandleFunc("/", indexHandler)
err := http.ListenAndServe(":8000", nil)
if err != nil {
panic(err)
}
}
client end
// context_timeout/client/main.go
package main
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
)
// 客户端
type respData struct {
resp *http.Response
err error
}
func doCall(ctx context.Context) {
transport := http.Transport{
// 请求频繁可定义全局的client对象并启用长链接
// 请求不频繁使用短链接
DisableKeepAlives: true, }
client := http.Client{
Transport: &transport,
}
respChan := make(chan *respData, 1)
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
if err != nil {
fmt.Printf("new requestg failed, err:%v\n", err)
return
}
req = req.WithContext(ctx) // 使用带超时的ctx创建一个新的client request
var wg sync.WaitGroup
wg.Add(1)
defer wg.Wait()
go func() {
resp, err := client.Do(req)
fmt.Printf("client.do resp:%v, err:%v\n", resp, err)
rd := &respData{
resp: resp,
err: err,
}
respChan <- rd
wg.Done()
}()
select {
case <-ctx.Done():
//transport.CancelRequest(req)
fmt.Println("call api timeout")
case result := <-respChan:
fmt.Println("call server api success")
if result.err != nil {
fmt.Printf("call server api failed, err:%v\n", result.err)
return
}
defer result.resp.Body.Close()
data, _ := ioutil.ReadAll(result.resp.Body)
fmt.Printf("resp:%v\n", string(data))
}
}
func main() {
// 定义一个100毫秒的超时
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)
defer cancel() // 调用cancel释放子goroutine资源
doCall(ctx)
}