当前位置:网站首页>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
边栏推荐
- CPU内核和逻辑处理器的区别
- 【Rust 笔记】17-并发(下)
- [jailhouse article] jailhouse hypervisor
- leetcode-6110:网格图中递增路径的数目
- 对for(var i = 0;i < 5;i++) {setTimeout(() => console.log(i),1000)}的深入分析
- [rust notes] 17 concurrent (Part 1)
- [article de jailhouse] jailhouse hypervisor
- Analysis of backdoor vulnerability in remote code execution penetration test / / phpstudy of national game title of national secondary vocational network security B module
- 7. Processing the input of multidimensional features
- leetcode-1200:最小绝对差
猜你喜欢
RGB LED infinite mirror controlled by Arduino
Appium自动化测试基础 — Appium测试环境搭建总结
Open source storage is so popular, why do we insist on self-development?
LeetCode 0108. Convert an ordered array into a binary search tree - the median of the array is the root, and the left and right of the median are the left and right subtrees respectively
The connection and solution between the shortest Hamilton path and the traveling salesman problem
Appium automation test foundation - Summary of appium test environment construction
Individual game 12
Liunx starts redis
[article de jailhouse] jailhouse hypervisor
Brief introduction to tcp/ip protocol stack
随机推荐
The difference between CPU core and logical processor
对for(var i = 0;i < 5;i++) {setTimeout(() => console.log(i),1000)}的深入分析
SQLMAP使用教程(二)实战技巧一
Redis publish subscribe command line implementation
Time of process
2022 pole technology communication arm virtual hardware accelerates the development of Internet of things software
1041 Be Unique
Multi screen computer screenshots will cut off multiple screens, not only the current screen
Wazuh开源主机安全解决方案的简介与使用体验
[rust notes] 16 input and output (Part 1)
SQLMAP使用教程(一)
Flutter Web 硬件键盘监听
QQ computer version cancels escape character input expression
Liunx starts redis
Simply sort out the types of sockets
做 SQL 性能优化真是让人干瞪眼
SPI 详解
Full Permutation Code (recursive writing)
leetcode-31:下一个排列
Common optimization methods