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)
}

写到这里, 忽然发现并没有泛型什么事情, 等下一次好机会...

<完>

使用 Discussions 讨论 Github 上编辑 分享到 Twitter