Golang的Panic包装实现以及引申的一点点思考
最近在清理之前收藏的文章, 其中有一段话是, "刚使用 Golang 的人很容易踩到的一个 goroutine 坑点是,一个 goroutine 如果 panic 了,在它的父 goroutine 是无法 recover 的——严格来讲,并没有父子 goroutine 的概念,一旦启动,就是一个独立的 goroutine 了"
这就很有意思了, 虽然现在有很多的开源实现, 但是我想自己写一个, 因为很明显的, 这里需要用到泛型. 而我之前没有实践过. 还是从最简单的部分开始.
func Go(fn func()) {
go func() {
defer func() {
if v := recover(); v != nil {
log.Printf("goroutine panic: %v\n", v)
}
}()
fn()
}()
}
func main() {
Go(func() {
panic("something went wrong")
})
Go(func() {
fmt.Println("hello from goroutine")
})
time.Sleep(time.Second)
}
但是这里有一个问题是, goroutine没有参数啊. 假设这里我需要传递一个in, 一个out 两个channel, 为了达到这个目的, 我想到了三种办法
package main
import (
"fmt"
"log"
"time"
)
func Go(fn func()) {
go func() {
defer func() {
if v := recover(); v != nil {
log.Printf("goroutine panic: %v\n", v)
}
}()
fn()
}()
}
// --- 方法 1:工厂函数,先绑 in/out,再返回 func() ---
func pipeWorker(in <-chan int, out chan<- int) func() {
return func() {
for v := range in {
out <- v * 2
}
close(out)
}
}
func example1() {
in := make(chan int, 3)
out := make(chan int, 3)
in <- 1
in <- 2
in <- 3
close(in)
Go(pipeWorker(in, out))
for v := range out {
fmt.Println("method 1:", v)
}
}
// --- 方法 2:结构体打包,参数与业务逻辑一并放入结构体 ---
type PipeWorker struct {
In <-chan string
Out chan<- string
Fn func(string) string
}
func (w PipeWorker) Run() {
for v := range w.In {
w.Out <- w.Fn(v)
}
close(w.Out)
}
func example2() {
in := make(chan string, 3)
out := make(chan string, 3)
in <- "go"
in <- "rust"
in <- "zig"
close(in)
w := PipeWorker{
In: in,
Out: out,
Fn: func(s string) string {
return "[" + s + "]"
},
}
Go(w.Run)
for v := range out {
fmt.Println("method 2:", v)
}
}
// --- 方法 3:直接闭包,捕获外层的 in/out ---
func example3() {
in := make(chan int, 3)
out := make(chan int, 3)
in <- 7
in <- 8
in <- 9
close(in)
Go(func() {
for v := range in {
out <- v + 1
}
close(out)
})
for v := range out {
fmt.Println("method 3:", v)
}
}
func main() {
example1()
example2()
example3()
time.Sleep(100 * time.Millisecond)
}
写到这里, 忽然发现并没有泛型什么事情, 等下一次好机会...
<完>