专栏名称: 狗厂
目录
相关文章推荐
潇湘晨报  ·  湖南邵阳一烧烤店推出“麻辣竹签”10元1份! ... ·  22 小时前  
潇湘晨报  ·  知名巨头宣布:裁员7500人! ·  3 天前  
51好读  ›  专栏  ›  狗厂

[英]理解Go中的context包

狗厂  · 掘金  ·  · 2018-07-10 07:09

正文


Understanding the context package in golang

The context package in go can come in handy while interacting with APIs and slow processes, especially in production-grade systems that serve web requests. Where, you might want to notify all the goroutines to stop work and return. Here is a basic tutorial on how you can use it in your projects with some best practices and gotchas.

For understanding the context package there are 2 concepts that you should be familiar with.

I will try to briefly cover these before moving on to context, if you are already familiar with these, you can move on directly to the Context section.

Goroutine

From the official go documentation: “A goroutine is a lightweight thread of execution”. Goroutines are lighter than a thread so managing them is comparatively less resource intensive.

Playground: play.golang.org/p/-TDMgnkJR…

package main
import "fmt"

//function to print hello
func printHello() {
fmt.Println("Hello from printHello")
}
func main() {
//inline goroutine. Define a function inline and then call it.
go func(){fmt.Println("Hello inline")}()
//call a function as goroutine
go printHello()
fmt.Println("Hello from main")
}

If you run the above program, you may only see it print out Hello from main that is because it fires up couple goroutines and the main function exits before any of them complete. To make sure main waits for the goroutines to complete, you will need some way for the goroutines to tell it that they are done executing, that’s where channels can help us.

Channels

These are the communication channels between goroutines. Channels are used when you want to pass in results or errors or any other kind of information from one goroutine to another. Channels have types, there can be a channel of type int to receive integers or error to receive errors, etc.

Say there is a channel ch of type int If you want to send something to a channel, the syntax is ch <- 1 if you want to receive something from the channel it will be var := <- ch . This recieves from the channel and stores the value in var .

The following program illustrates the use of channels to make sure the goroutines complete and return a value from them to main.

Note: Wait groups ( https://golang.org/pkg/sync/#WaitGroup ) can also be used for synchronization, but since we discuss channels later on in the context section, I am picking them in my code samples for this blog post

Playground: play.golang.org/p/3zfQMox5m…

package main
import "fmt"

//prints to stdout and puts an int on channel
func printHello(ch chan int) {
fmt.Println("Hello from printHello")
//send a value on channel
ch <- 2
}
func main() {
//make a channel. You need to use the make function to create channels.
//channels can also be buffered where you can specify size. eg: ch := make(chan int, 2)
//that is out of the scope of this post.
ch := make(chan int)
//inline goroutine. Define a function and then call it.
//write on a channel when done
go func(){
fmt.Println("Hello inline")
//send a value on channel
ch <- 1
}()
//call a function as goroutine
go printHello(ch)
fmt.Println("Hello from main")
//get first value from channel.
//and assign to a variable to use this value later
//here that is to print it
i := <- ch
fmt.Println("Recieved ",i)
//get the second value from channel
//do not assign it to a variable because we dont want to use that
<- ch
}

A way to think about context package in go is that it allows you to pass in a “context” to your program. Context like a timeout or deadline or a channel to indicate stop working and return. For instance, if you are doing a web request or running a system command, it is usually a good idea to have a timeout for production-grade systems. Because, if an API you depend on is running slow, you would not want to back up requests on your system, because, it may end up increasing the load and degrading the performance of all the requests you serve. Resulting in a cascading effect. This is where a timeout or deadline context can come in handy.

Creating context

The context package allows creating and deriving context in following ways:

context.Background() ctx Context

This function returns an empty context. This should be only used at a high level (in main or the top level request handler). This can be used to derive other contexts that we discuss later.

ctx, cancel := context.Background()

context.TODO() ctx Context

This function also creates an empty context. This should also be only used at a high level or when you are not sure what context to use or if the function has not been updated to receive a context. Which means you (or the maintainer) plans to add context to the function in future.

ctx, cancel := context.TODO()

Interestingly, looking at the code ( golang.org/src/context… ), it is exactly same as background. The difference is, this can be used by static analysis tools to validate if the context is being passed around properly, which is an important detail, as the static analysis tools can help surface potential bugs early on, and can be hooked up in a CI/CD pipeline.

From golang.org/src/context…

var ( background = new(emptyCtx) todo = new(emptyCtx) )复制代码

context.WithValue(parent Context, key, val interface{}) (ctx Context, cancel CancelFunc)

This function takes in a context and returns a derived context where value val is associated with key and flows through the context tree with the context. This means that once you get a context with value, any context that derives from this gets this value. It is not recommended to pass in critical parameters using context value, instead, functions should accept those values in the signature making it explicit.

ctx := context.WithValue(context.Background(), key, "test")

context.WithCancel(parent Context) (ctx Context, cancel CancelFunc)

This is where it starts to get a little interesting. This function creates a new context derived from the parent context that is passed in. The parent can be a background context or a context that was passed into the function.

This returns a derived context and the cancel function. Only the function that creates this should call the cancel function to cancel this context. You can pass around the cancel function if you wanted to, but, that is highly not recommended. This can lead to the invoker of cancel not realizing what the downstream impact of canceling the context may be. There may be other contexts that are derived from this which may cause the program to behave in an unexpected fashion. In short, NEVER pass around the cancel function.

ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2 * time.Second))

context.WithDeadline(parent Context, d time.Time) (ctx Context, cancel CancelFunc)

This function returns a derived context from its parent that gets cancelled when the deadline exceeds or cancel function is called. For example, you can create a context that will automatically get canceled at a certain time in future and pass that around in child functions. When that context gets canceled because of deadline running out, all the functions that got the context get notified to stop work and return.

ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2 * time.Second))

context.WithTimeout(parent Context, timeout time.Duration) (ctx Context, cancel CancelFunc)

This function is similar to context.WithDeadline . The difference is that it takes in time duration as an input instead of the time object. This function returns a derived context that gets canceled if the cancel function is called or the timeout duration is exceeded.

ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2 * time.Second))

Accepting and using contexts in your functions

Now that we know how to create the contexts (Background and TODO) and how to derive contexts (WithValue, WithCancel, Deadline, and Timeout), let’s discuss how to use them.

In the following example, you can see a function accepting context starts a goroutine and waits for that goroutine to return or that context to cancel. The select statement helps us to pick whatever case happens first and return.

<-ctx.Done() once the Done channel is closed, the case <-ctx.Done(): is selected. Once this happens, the function should abandon work and prepare to return. That means you should close any open pipes, free resources and return form the function. There are cases when freeing up resources can hold up the return, like doing some clean up that hangs, etc. You should look out for any such possibilities while handling the context return.







请到「今天看啥」查看全文