This is a creation in Article, where the information may have evolved or changed.
Brief introduction
As a modern language, the go language implements native support for concurrency. In previous installments, we gave a detailed explanation of the goroutine and channel. But in order to control the channel, the SELECT statement is an essential part of the language level. In this article, we discuss the behavior of the SELECT statement and how to use it in depth.
Read suggestions
The content in this article is a part of the Go language concurrency model, but it is not particularly close to the previous installments and can be read independently. The content of this article stems from a speech called "Concurrency" by Go language specifications and Rob Pike in 2012. If you have time, it is advisable to take a look at his speech on YouTube.
Behavior of the SELECT statement
For the sake of understanding, we first give a snippet of code:
// https://talks.golang.org/2012/concurrency.slide#32select {case v1 := <-c1: fmt.Printf("received %v from c1\n", v1)case v2 := <-c2: fmt.Printf("received %v from c2\n", v1)case c3 <- 23: fmt.Printf("sent %v to c3\n", 23)default: fmt.Printf("no one was ready to communicate\n")}
In the above code, the SELECT statement has four case statements, the first two are receive operations, the third is the send operation, and the last is the default action. When the code executes to select, the case statement is evaluated in the order of the source code and evaluated only once, and the results of the evaluation can occur in the following cases:
In addition to the default, if only one case statement is passed, execute the statement in it;
In addition to the default, if there are multiple case statements evaluated through, then randomly selected by pseudo-random method;
If the case statement outside of default does not pass the evaluation, then execute the statement in default;
If there is no default, then blocks of code will be blocked, directing a case to pass the evaluation;
If the object of the receive operation in the case statement is the nil channel, it will also block, so let's look at a more comprehensive, more advanced example:
// https://golang.org/ref/spec#Select_statementsvar a []intvar c, c1, c2, c3, c4 chan intvar i1, i2 intselect {case i1 = <-c1: print("received ", i1, " from c1\n")case c2 <- i2: print("sent ", i2, " to c2\n")case i3, ok := (<-c3): // same as: i3, ok := <-c3 if ok { print("received ", i3, " from c3\n") } else { print("c3 is closed\n") }case a[f()] = <-c4: // same as: // case t := <-c4 // a[f()] = tdefault: print("no communication\n")}for { // 向 channel c 发送随机 bit 串 select { case c <- 0: // note: no statement, no fallthrough, no folding of cases case c <- 1: }}select {} // 永久阻塞
Note: Unlike traditional programming languages such as C + +, the Go Language case statement does not require the break keyword to jump out of select.
Use of Select
Set the time-out for a request
Before Golang 1.7, HTTP packets were not introduced to the context support via HTTP. A Client sending a request to a broken service can cause a slow response. In a similar scenario, we can use Select to control the response time of the service, and here is a simple demo:
func main() { c := boring("Joe") timeout := time.After(5 * time.Second) for { select { case s := <-c: fmt.Println(s) case <-timeout: fmt.Println("You talk too much.") return } }}
Done Channel
In previous installments, we have discussed the done channel, which can be used to ensure that goroutine exits at each stage of the pipeline. In the Golang.org/x/net package, the done channel is widely used. Here we take a look at the implementation of the Do method in Golang.org/x/net/context/ctxhttp:
// https://github.com/golang/net/blob/release-branch.go1.7/context/ctxhttp/ctxhttp.go// Do sends an HTTP request with the provided http.Client and returns// an HTTP response.//// If the client is nil, http.DefaultClient is used.//// The provided ctx must be non-nil. If it is canceled or times out,// ctx.Err() will be returned.func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { if client == nil { client = http.DefaultClient } resp, err := client.Do(req.WithContext(ctx)) // If we got an error, and the context has been canceled, // the context's error is probably more useful. if err != nil { select { case <-ctx.Done(): err = ctx.Err() default: } }
Quit Channel
In many scenarios, the Quit channel and the done channel are a concept. In concurrent programs, it is common for main routine to assign tasks to other go routine, which is just a function of scheduling. In this case, the main function does not know whether the other Goroutine tasks are complete, we need the Quit channel at this point, and for finer-grained controls, such as how much to complete, or the done channel (refer to Waitgroup). Here is an example of the Quit channel, first of which is main routine:
// 创建 quit channelquit := make(chan string)// 启动生产者 goroutinec := boring("Joe", quit)// 从生产者 channel 读取结果for i := rand.Intn(10); i >= 0; i-- { fmt.Println(<-c) }// 通过 quit channel 通知生产者停止生产quit <- "Bye!"fmt.Printf("Joe says: %q\n", <-quit)
Let's look at the parts related to the Quit channel in producer go routine:
select {case c <- fmt.Sprintf("%s: %d", msg, i): // do nothingcase <-quit: cleanup() quit <- "See you!" return}
Google Search (extended reading)
Google Search is a classic example, because of the more code, interested in children's shoes to see Rob Pike ppt.
Higher-order concurrency can be read Sameer ajmani ppt advanced Go Concurrency Patterns
The topic of concurrency is here first, and in the next installment, we'll discuss unit testing in the Go Language testing tool chain.
RELATED links:
Rob Pike speech: concurrency
Language Specification:select Statement
Sweep code attention to the public number "go language deep"