当前位置:网站首页>Golang uses context gracefully
Golang uses context gracefully
2022-07-05 06:12:00 【Dawnlighttt】
List of articles
Elegant closure goroutine
In many scenarios , When performing a task , We will split this task into several subtasks , Then open several different goroutine To carry out . When the task needs to be terminated for some reason , We need to put these goroutine And it's all over .
such as Go http Bag Server in , Each request has a corresponding goroutine To deal with . Request processing functions usually start additional goroutine Used to access back-end services , Such as databases and RPC service . Used to process a request goroutine You usually need to access some data specific to the request , For example, the identity authentication information of the end user 、 Verify the relevant token、 Deadline for requests . When a request is cancelled or timed out , All that is used to process the request goroutine You should quit quickly , Then the system can release these goroutine Occupied resources .
Let's take a simple example , Then use different methods to close the goroutine.
sync.WaitGroup Realization
package main
import (
"fmt"
"strconv"
"sync"
"time"
)
var wg sync.WaitGroup
func run(task string) {
fmt.Println(task, "start...")
time.Sleep(time.Second * 2)
// Every groutine Release after running WaitGroup The timer of
wg.Done()
}
func main() {
wg.Add(2)
for i := 1; i < 3; i++ {
taskName := "task" + strconv.Itoa(i)
go run(taskName)
}
wg.Wait()
fmt.Println(" All the missions are over ")
}
// task2 start...
// task1 start...
// All the missions are over
In the example above , When a task is over, you must wait for another task to be over before it is all over , What is completed first must wait for other unfinished , be-all goroutine All must be completed before OK.
Advantages of this way : Use the concurrency control model of waiting group , It is especially suitable for many goroutine When working together to do one thing , Because of every goroutine It's all part of it , Only all goroutine It's all done , This thing is finished .
The drawbacks of this approach : In actual production , We need to take the initiative to inform someone goroutine end .
channel + select Realization
package main
import (
"fmt"
"time"
)
func main() {
stop := make(chan bool)
go func() {
for {
select {
case <- stop:
fmt.Println(" Mission 1 It's over ")
return
default:
fmt.Println(" Mission 1 Running ")
time.Sleep(time.Second)
}
}
}()
// function 5 Seconds after stop
time.Sleep(time.Second * 5)
stop <- true
// Stop testing goroutine Is it over
time.Sleep(time.Second * 3)
}
// Mission 1 Running
// Mission 1 Running
// Mission 1 Running
// Mission 1 Running
// Mission 1 Running
// Mission 1 It's over
Use channel + select The advantages of : More elegant .
Use channel + select The shortcomings of : If there are more than one goroutine What to do if it needs to be closed ? You can use the global bool Methods of type variables , But when assigning values to global variables, locks are needed to ensure the safety of the coroutine , This is bound to affect convenience and performance . What is more , If each goroutine Nested in goroutine Well ?
context Realization
Use the above code context rewrite :
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
// Turn on goroutine, Pass in ctx
go func(ctx context.Context) {
for {
select {
case <- ctx.Done():
fmt.Println(" Mission 1 It's over ")
return
default:
fmt.Println(" Mission 1 Running ")
time.Sleep(time.Second)
}
}
}(ctx)
// Stop after five seconds
time.Sleep(time.Second * 5)
cancel()
// Stop testing goroutine Is it over
time.Sleep(time.Second * 3)
}
// Mission 1 Running
// Mission 1 Running
// Mission 1 Running
// Mission 1 Running
// Mission 1 Running
// Mission 1 It's over
Use context Rewriting is relatively simple , Of course, the above is just a start goroutine The situation of , If there are more than one goroutine Well ?
package main
import (
"context"
"fmt"
"time"
)
// Use context Control multiple goroutine
func watch(ctx context.Context, name string) {
for {
select {
case <- ctx.Done():
fmt.Println(name, " sign out , Stopped. ")
return
default:
fmt.Println(name, " Running ")
time.Sleep(time.Second)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go watch(ctx, " Mission 1")
go watch(ctx, " Mission 2")
go watch(ctx, " Mission 3")
time.Sleep(time.Second * 3)
// Notify the task to stop
cancel()
time.Sleep(time.Second * 2)
fmt.Println(" Make sure all tasks stop ")
}
// Mission 3 Running
// Mission 1 Running
// Mission 2 Running
// Mission 2 Running
// Mission 3 Running
// Mission 1 Running
// Mission 1 Running
// Mission 2 Running
// Mission 3 Running
// Mission 2 sign out , Stopped.
// Mission 1 sign out , Stopped.
// Mission 3 sign out , Stopped.
// Make sure all tasks stop
Above Context Like a controller , After pressing the switch , All based on this Context Or a derivative Context Will be notified , At this time, the cleaning operation can be carried out , Finally release goroutine, This is an elegant solution goroutine Uncontrollable problems after startup .
Gracefully close multiple goroutine nesting
package main
import (
"context"
"fmt"
"time"
)
// Define an inclusion context New type of
type otherContext struct {
context.Context
}
func work(ctx context.Context, name string) {
for {
select {
case <- ctx.Done():
fmt.Println(name, " get msg to cancel")
return
default:
fmt.Println(name, " is running")
time.Sleep(time.Second)
}
}
}
func workWithValue(ctx context.Context, name string) {
for {
select {
case <- ctx.Done():
fmt.Println(name, " get msg to cancel")
return
default:
value := ctx.Value("key").(string)
fmt.Println(name, " is running value = ", value)
time.Sleep(time.Second)
}
}
}
func main() {
// Use context.Background() Construct a WithCancel Type of context
ctxa, cancel := context.WithCancel(context.Background())
// work Simulate the operation and detect the exit notification of the front end
go work(ctxa, "work1")
// Use WithDeadline Wrap the context object in front ctxa
tm := time.Now().Add(3 * time.Second)
ctxb, _ := context.WithDeadline(ctxa, tm)
go work(ctxb, "work2")
// Use WithValue Wrap the context object in front ctxb
oc := otherContext{
ctxb}
ctxc := context.WithValue(oc, "key", "andes, pass from main")
go workWithValue(ctxc, "work3")
// deliberately "sleep" 10 second , Give Way work2、work3 Timeout exit
time.Sleep(10 * time.Second)
// Display call work1 Of cancel Method to notify it to exit
cancel()
// wait for work1 Print exit message
time.Sleep(5 *time.Second)
fmt.Println("main stop")
}
//work3 is running value = andes, pass from main
//work1 is running
//work2 is running
//work2 is running
//work3 is running value = andes, pass from main
//work1 is running
//work1 is running
//work3 is running value = andes, pass from main
//work2 is running
//work3 get msg to cancel
//work2 get msg to cancel
//work1 is running
//work1 is running
//work1 is running
//work1 is running
//work1 is running
//work1 is running
//work1 is running
//work1 get msg to cancel
//main stop
In the use of Context In the process of , The program actually maintains two relationship chains at the bottom .
children key From root to leaf Context The reference relationship of the instance , Calling With Function time , Would call propagateCancel(parent Context, child canceler) Function for maintenance , The program has such a tree structure :
ctxa.children --> ctxb ctxb.children --> ctxc
This tree provides a way to traverse the tree from the root node ,context The packet cancellation notification is based on this tree , The cancellation notification is broadcast layer by layer from the root node to the lower node along this chain . Of course, you can also call cancel notification on any subtree , It will also spread to the whole tree .
In the structure context Constantly wrapped in the object of context Instances form a chain of reference relationships , The direction of this relationship chain is the opposite , It's from the bottom up .
ctxc.Context --> oc ctxc.Context.Context --> ctxb ctxc.Context.Context.cancelCtx --> ctxa ctxc.Context.Context.Context.cancelCtx.Context -> new(emptyCtx) // context.Background()
This relationship chain is mainly used to cut off the current Context Instance and upper Context The relationship between instances ., such as ctxb Exit notification called or timer expired , ctxb There is no need to continue to exist on the notification broadcast tree , It needs to find its own parent , And then execute delete(parent.children,ctxb), Get rid of yourself from the radio tree .
net/http In bag context
net/http Package source code is being implemented http server When it comes to context, Let's briefly analyze :
1. First Server A... Is created when the service is opened valueCtx, Store server Information about , After that, each connection will start a process , And carry this valueCtx.
func (srv *Server) Serve(l net.Listener) error {
...
var tempDelay time.Duration // how long to sleep on accept failure
baseCtx := context.Background() // base is always background, per Issue 16220
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, e := l.Accept()
...
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}
2. The connection will be based on the incoming context Create a valueCtx Used to store local address information , Then on this basis, we created another cancelCtx, Then start reading network requests from the current connection , Every time a request is read, the cancelCtx Pass in , Used to transmit cancellation signals . Once the connection is broken , You can send a cancellation signal , Cancel all network requests in progress .
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
...
ctx, cancelCtx := context.WithCancel(ctx)
c.cancelCtx = cancelCtx
defer cancelCtx()
...
for {
w, err := c.readRequest(ctx)
...
serverHandler{
c.server}.ServeHTTP(w, w.req)
...
}
}
3. After reading the request , Again, based on the incoming context Create a new cancelCtx, And set to the current request object req On , Generated at the same time response In the object cancelCtx Saved the current context Cancellation method .
func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
...
req, err := readRequest(c.bufr, keepHostHeader)
...
ctx, cancelCtx := context.WithCancel(ctx)
req.ctx = ctx
...
w = &response{
conn: c,
cancelCtx: cancelCtx,
req: req,
reqBody: req.Body,
handlerHeader: make(Header),
contentLength: -1,
closeNotifyCh: make(chan bool, 1),
// We populate these ahead of time so we're not
// reading from req.Header after their Handler starts
// and maybe mutates it (Issue 14940)
wants10KeepAlive: req.wantsHttp10KeepAlive(),
wantsClose: req.wantsClose(),
}
...
return w, nil
}
The main purposes of this treatment are as follows :
- Once the request times out , To interrupt the current request ;
- In the process of building response If something goes wrong in the process , Directly callable response Object's cancelCtx Method to end the current request ;
- In the process of building response When it's done , call response Object's cancelCtx Method to end the current request .
Throughout server In the process , Used a context Chain penetration Server、Connection、Request, Not only will upstream information be shared with downstream tasks , At the same time, the upstream can send cancel signals to cancel all the downstream tasks , The cancellation of the downstream task will not affect the upstream task .
About Context To transfer data
First of all, we should clearly use context Package mainly solves goroutine Notification exit of , Transferring data is an additional function .
You can use it to pass some meta information , In short, use context The information transmitted cannot affect the normal business process , Don't expect the program to context Pass some necessary parameters in , Without these parameters , The program should also work properly .
context What data should be transferred
1. Log information .
2. Debugging information .
3. Optional data that does not affect the main business logic .
Summary
1.context It is mainly used for the synchronization cancellation signal between parent-child tasks , In essence, it is a way of scheduling . In addition, we are using context There are two points worth noting : Upstream tasks only use context Notify downstream tasks that no longer need , But it will not directly interfere with and interrupt the execution of downstream tasks , It's up to the downstream task to decide the subsequent processing operation , in other words context The cancellation of is non intrusive ;context It's thread safe , because context It is immutable in itself (immutable), Therefore, it can be safely used in multiple processes .
2.context The core functions provided by the package are multiple goroutine Exit notification mechanism between , Transferring data is only an auxiliary function , Use with caution context To transfer data .
Reference material :
《Go Language core programming 》
https://www.cnblogs.com/vinsent/p/11455531.html
https://zhuanlan.zhihu.com/p/110085652
边栏推荐
- Doing SQL performance optimization is really eye-catching
- Flutter Web 硬件键盘监听
- API related to TCP connection
- MIT-6874-Deep Learning in the Life Sciences Week 7
- Appium automation test foundation - Summary of appium test environment construction
- Traditional databases are gradually "difficult to adapt", and cloud native databases stand out
- 1.14 - 流水线
- Personal developed penetration testing tool Satania v1.2 update
- LeetCode 0107.二叉树的层序遍历II - 另一种方法
- QT判断界面当前点击的按钮和当前鼠标坐标
猜你喜欢
Traditional databases are gradually "difficult to adapt", and cloud native databases stand out
快速使用Amazon MemoryDB并构建你专属的Redis内存数据库
Leetcode-6108: decrypt messages
Individual game 12
API related to TCP connection
Dynamic planning solution ideas and summary (30000 words)
Real time clock (RTC)
数据可视化图表总结(二)
On the characteristics of technology entrepreneurs from Dijkstra's Turing Award speech
[jailhouse article] look mum, no VM exits
随机推荐
Some common problems in the assessment of network engineers: WLAN, BGP, switch
Full Permutation Code (recursive writing)
Common optimization methods
Basic explanation of typescript
leetcode-31:下一个排列
leetcode-6108:解密消息
Leetcode-1200: minimum absolute difference
liunx启动redis
[jailhouse article] performance measurements for hypervisors on embedded ARM processors
Doing SQL performance optimization is really eye-catching
Smart construction site "hydropower energy consumption online monitoring system"
【Rust 笔记】16-输入与输出(上)
Matrixdb V4.5.0 was launched with a new mars2 storage engine!
Introduction to LVS [unfinished (semi-finished products)]
CPU内核和逻辑处理器的区别
Convolution neural network -- convolution layer
【Rust 笔记】13-迭代器(中)
Appium automation test foundation - Summary of appium test environment construction
打印机脱机时一种容易被忽略的原因
MIT-6874-Deep Learning in the Life Sciences Week 7