background
In Go, goroutine creation cost is low and scheduling efficiency is high. It is not surprising that there are hundreds of thousands of Goroutine at the same time. Although the memory used by a single Goroutine is limited, it does not mean that there is no limit to creating goroutines.
Never start a goroutine without knowing how it will stop
Each time you start the Goroutine, you must know when and how the Goroutine exits. Otherwise, the program is potentially a memory leak. Before we talk about coroutines exiting, why do coroutines block
Coroutines blocking
Coroutine blocking cannot exit freely, mainly because of two things:
- Timeout control
- Process control
context
The former is easy to understand. When a Goroutine is started to process a transaction, it is expected that the transaction will complete at a certain time.
- RPC call: The maximum timeout period does not exceed the waiting time of the user
- Scheduled task: The execution time should not exceed the startup interval
For when to exit, Go provides a Context for Goroutine lifecycle management
Cancellation via context.WithCancel.
Timeout via context.WithDeadline.
req, err := http.NewRequest("GET"."https://play.golang.org/".nil) iferr ! =nil { log.Fatalf("%v", err) } ctx, cancel := context.WithTimeout(req.Context(), 1*time.Second) defer cancel() req = req.WithContext(ctx) client := http.DefaultClient resp, err := client.Do(req) iferr ! =nil { log.Fatalf("%v", err) } fmt.Printf("%v\n", resp.StatusCode) Copy the code
channel & select
The latter is more difficult to understand. For speakers of other languages in particular, flow control in a program generally means:
- if/else
- for loop
In Go, a similar understanding is only half right. Because channel and SELECT are the focus of process control.
Channels provide a powerful ability to flow data from one Goroutine to another. It also means that a channel affects both the data flow and the control flow of a program.
A closed channel will never block
package main import "fmt" func main(a) { ch := make(chan bool.2) ch <- true ch <- true close(ch) for v := range ch { fmt.Println(v) // called twice}}Copy the code
Nil channels always block
package main func main(a) { var ch chan bool ch <- true // blocks forever } Copy the code
A buffered/unbuffered channel is somewhere in between and is blocked because of whether the channel can be read or written
So how do you tell if a channel is read or write? The answer is select.
select{
case channel_send_or_receive:
//Dosomething
case channel_send_or_receive:
//Dosomething
default:
//Dosomething
}
Copy the code
Coroutines exit
With all that said, how do coroutines exit? I believe it is easy to get the conclusion from the above part:
- Timeout return
- Returns according to the channel readable status
// Method 1: traversal the closed channel
for x := range closedCh {
fmt.Printf("Process %d\n", x)
}
// Method 2: Select a readable channel
for {
select {
case <-stopCh:
fmt.Println("Recv stop signal")
return
case <-t.C:
fmt.Println("Working .")}}Copy the code
Perfect exit
Is it enough that coroutines can exit? The perfect exit should include three things:
- Notifying coroutine to exit
- Notification confirmation, coroutine exits
- Gets the error that the coroutine eventually returns
For example: ErrGroup
func (g *Group) Wait(a) error { g.wg.Wait() ifg.cancel ! =nil { g.cancel() } return g.err } Copy the code
This article is written by Cyningsun at www.cyningsun.com/01-31-2021/… Copyright Notice: All articles in this blog are under the CC BY-NC-ND 3.0CN license unless otherwise stated. Reprint please indicate the source!