工作中需要启用多个Goroutine,又想要管理好Goruntine,如何做到统一管理Goroutine。就需要使用Context包和channel处理了。
select 会阻塞等待channel消息,如果有满足则执行并退出,否则一直阻塞等待。
Go 语言 select 关键字
golang context官方文档
Go Context的踩坑经历
理解 golang 中的 context(上下文) 包
代码来自于上述文章。
package main
import (
"context"
"fmt"
"math/rand"
"time"
)
//Slow function
func sleepRandom(fromFunction string, ch chan int) {
//defer cleanup
defer func() { fmt.Println(fromFunction, "sleepRandom complete") }()
//Perform a slow task
//For illustration purpose,
//Sleep here for random ms
seed := time.Now().UnixNano()
r := rand.New(rand.NewSource(seed))
randomNumber := r.Intn(100)
sleeptime := randomNumber + 100
fmt.Println(fromFunction, "Starting sleep for", sleeptime, "ms")
time.Sleep(time.Duration(sleeptime) * time.Millisecond)
fmt.Println(fromFunction, "Waking up, slept for ", sleeptime, "ms")
//write on the channel if it was passed in
if ch != nil {
ch <- sleeptime
}
}
//Function that does slow processing with a context
//Note that context is the first argument
func sleepRandomContext(ctx context.Context, ch chan bool) {
//Cleanup tasks
//There are no contexts being created here
//Hence, no canceling needed
defer func() {
fmt.Println("sleepRandomContext complete")
ch <- true
}()
//Make a channel
sleeptimeChan := make(chan int)
//Start slow processing in a goroutine
//Send a channel for communication
go sleepRandom("sleepRandomContext", sleeptimeChan)
//Use a select statement to exit out if context expires
select {
case <-ctx.Done():
//If context is cancelled, this case is selected
//This can happen if the timeout doWorkContext expires or
//doWorkContext calls cancelFunction or main calls cancelFunction
//Free up resources that may no longer be needed because of aborting the work
//Signal all the goroutines that should stop work (use channels)
//Usually, you would send something on channel,
//wait for goroutines to exit and then return
//Or, use wait groups instead of channels for synchronization
fmt.Println("sleepRandomContext: Time to return")
case sleeptime := <-sleeptimeChan:
//This case is selected when processing finishes before the context is cancelled
fmt.Println("Slept for ", sleeptime, "ms")
}
}
//A helper function, this can, in the real world do various things.
//In this example, it is just calling one function.
//Here, this could have just lived in main
func doWorkContext(ctx context.Context) {
//Derive a timeout context from context with cancel
//Timeout in 150 ms
//All the contexts derived from this will returns in 150 ms
ctxWithTimeout, cancelFunction := context.WithTimeout(ctx, time.Duration(150)*time.Millisecond)
//Cancel to release resources once the function is complete
defer func() {
fmt.Println("doWorkContext complete")
cancelFunction()
}()
//Make channel and call context function
//Can use wait groups as well for this particular case
//As we do not use the return value sent on channel
ch := make(chan bool)
go sleepRandomContext(ctxWithTimeout, ch)
//Use a select statement to exit out if context expires
select {
case <-ctx.Done():
//This case is selected when the passed in context notifies to stop work
//In this example, it will be notified when main calls cancelFunction
fmt.Println("doWorkContext: Time to return")
case <-ch:
//This case is selected when processing finishes before the context is cancelled
fmt.Println("sleepRandomContext returned")
}
}
func main() {
//Make a background context
ctx := context.Background()
//Derive a context with cancel
ctxWithCancel, cancelFunction := context.WithCancel(ctx)
//defer canceling so that all the resources are freed up
//For this and the derived contexts
defer func() {
fmt.Println("Main Defer: canceling context")
cancelFunction()
}()
//Cancel context after a random time
//This cancels the request after a random timeout
//If this happens, all the contexts derived from this should return
go func() {
sleepRandom("Main", nil)
cancelFunction()
fmt.Println("Main Sleep complete. canceling context")
}()
//Do work
doWorkContext(ctxWithCancel)
}
代码包含一个150ms自动取消的context,和一个随机sleep一定时间的Goruntine,所以会出现两种情况:1、sleep超过150ms 通过定时context取消。 2、sleep小于150ms 通过管道通知结束。
Main Starting sleep for 168 ms
sleepRandomContext Starting sleep for 188 ms
sleepRandomContext: Time to return
sleepRandomContext complete
sleepRandomContext returned
doWorkContext complete
Main Defer: canceling context
ctxWithTimeout, cancelFunction := context.WithTimeout(ctx, time.Duration(150)*time.Millisecond)
150ms后sleepRandomContext
触发cancel, select 中 ctx.Done() 接收到消息,打印:sleepRandomContext: Time to return
接着执行sleepRandomContext
的defer 打印 :
sleepRandomContext complete
defer内通过管道通知到doWorkContext
,打印:
sleepRandomContext returned
再defer,打印:
doWorkContext complete
再defer 打印:
Main Defer: canceling context
sleepRandomContext Starting sleep for 101 ms
Main Starting sleep for 170 ms
sleepRandomContext Waking up, slept for 101 ms
sleepRandomContext sleepRandom complete
Slept for 101 ms
sleepRandomContext complete
sleepRandomContext returned
doWorkContext complete
Main Defer: canceling context
101ms后sleepRandom
通过channel通知,先defer:
sleepRandomContext sleepRandom complete
然后select接收到:
Slept for 101 ms
接着执行defer 打印 :
sleepRandomContext complete
defer内通过管道通知到doWorkContext
,打印:
sleepRandomContext returned
再defer,打印:
doWorkContext complete
再defer 打印:
Main Defer: canceling context
理解GO CONTEXT机制
package main
import (
"fmt"
"time"
"context"
)
func HandelRequest(ctx context.Context) {
go WriteRedis(ctx)
go WriteDatabase(ctx)
for {
select {
case <-ctx.Done():
fmt.Println("HandelRequest Done.")
return
default:
fmt.Println("HandelRequest running")
time.Sleep(2 * time.Second)
}
}
}
func WriteRedis(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("WriteRedis Done.")
return
default:
fmt.Println("WriteRedis running")
time.Sleep(2 * time.Second)
}
}
}
func WriteDatabase(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("WriteDatabase Done.")
return
default:
fmt.Println("WriteDatabase running")
time.Sleep(2 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go HandelRequest(ctx)
time.Sleep(5 * time.Second)
fmt.Println("It's time to stop all sub goroutines!")
cancel()
//Just for test whether sub goroutines exit or not
time.Sleep(5 * time.Second)
}
main协程在适当的时机可以cancel掉所有子协程。