[goroutine] is a pkg which provides safe usage of using golang goroutine.
To get well divided, goroutine
is described as golang official object, and [goroutine]
is described for this repo.
[goroutine] is not redesigning another goroutine. It's just a wrap to make goroutine perform better in group team.
Golang basically supports goroutine and it's easy to use , like go handler()
.
It's just easy and convenient, however!
Most of you should have been faced with these kind of questions:
- A goroutine function panics and trigger your application node hung up or restart. [This is extremely fatal!!]
- A goroutine once started, it's hardly being monitored. [There're too many goroutines in pprof/goroutine, hardly locate what you want!]
- Most memory leak (by goroutine bursting) is caused by mis-understanding lifetime of a goroutine, this package force user to estimate how long your goroutine should have. Err will be reported if unexpected alive goroutine occurs.
And thus, using [goroutine] will improve all advantages below:
- Auto recovers if goroutine meets an inner panic.Besides, how to handle a panic can be set by
goroutine.HandlePanic = func(e interface{}) {}
. - Monitor zombie goroutines.
- Alert and report those zombie goroutines.
Know that using [goroutine] will have some extra cost for monitoring a goroutine, they're:
- An extra goroutine to wait core goroutine to finish or timeout warning.
- Each goroutine will be managed through a gss center, it's a concurrent map (It means two irrelevant goroutines are put into a race container, there should be lock cost too).
But compare with your service logic, these cost is few.
In which case you should use [goroutine]:
- Most of your teammates are new. They're ones easier to produce panic.
- Some of your jobs are important and just put into product use (it might cause unexpected leak or panic and thus should be monitored for some time).
- Most of your service project creating goroutine.
In which case you should use official goroutine:
- You're designing a framework which has nothing to do with business.
- You're excellent with goroutine and hardly produce unexpected zombie goroutines.
- You've owned well-performed toolkit to detect goroutine leak and panic.
- You just want to use goroutine as fast,clean as it can.
To well divide which case to use and which not, here is some list:
✔ means should use [goroutine] ✖ means should use golang official goroutine
scene | should use | reason |
---|---|---|
start a cron job | ✔ | each job should have expected deadline, if hard to decide, set it bigger |
write a log asynchronously | ✔ | the same reason ↑ |
heartbeat routine on user connects | ✖ | this should well handled in framework, normally untouchable by teammate |
jobs with sync.waitGroup |
✔✖ | If job not done or expire, waitGroup will block which break your request, this is easy detect thus use [goroutine] or not both ok |
present extra props in shop exchange | ✔ | goroutine for service logic should always be well protected. These codes are often touched by teammates |
go get github.com/fwhezfwhez/goroutine
package main
import (
"fmt"
"goroutine"
"math/rand"
"runtime/debug"
"time"
)
func init() {
// config zombie storage time
goroutine.ZombieStorageSeconds = 3 * 24 * 60 * 60
// specific method to handle panic in goroutine
goroutine.HandlePanic = func(e interface{}) {
fmt.Printf("recv a panic from protected-go: %s \n %s \n", fmt.Sprintf("%v", e), debug.Stack())
}
// specific method to alert a bad goroutine.
goroutine.HandleBadGs = func(gs *goroutine.Gs) {
fmt.Printf("recv a bad goroutine: %s\n", gs.Info())
}
// Each 5 seconds spying on zombie goroutines
go func() {
for {
info := goroutine.GlobalGss().Monitoring(10)
fmt.Println(info)
time.Sleep(5 * time.Second)
}
}()
}
func main() {
// normal goroutines with short deadline
for i := 0; i < 10000; i ++ {
goroutine.ProtectedGo(func() {
fmt.Println("normal goroutine")
}, goroutine.GoParam{
UnqKey: fmt.Sprintf("test_protected_go_normal_goroutine_%d", i),
ExpectedExpireSecond: 1,
ShouldProtected: true,
})
}
// An eternal goroutine without deadline
goroutine.ProtectedGo(func() {
fmt.Println("an eternal goroutine")
}, goroutine.GoParam{
UnqKey: "test_protected_go_eternal_goroutine",
ExpectedExpireSecond: -1,
ShouldProtected: true,
})
// zombie goroutines. It expects 3s to run however lasting for 5 seconds
for i := 0; i < 1000; i ++ {
go func(i int) {
time.Sleep(time.Duration(rand.Intn(10)%15) * time.Second)
goroutine.ProtectedGo(func() {
fmt.Println("a being zombie goroutine")
time.Sleep(5 * time.Second)
}, goroutine.GoParam{
UnqKey: fmt.Sprintf("test_protected_go_zombie_goroutine_%d", i),
ExpectedExpireSecond: 3,
ShouldProtected: true,
})
}(i)
}
select {}
}