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