乐趣区

关于golang:Go-面试官进程线程都有-ID为什么-Goroutine-没有-ID

微信搜寻【脑子进煎鱼了】关注这一只爆肝煎鱼。本文 GitHub github.com/eddycjy/blog 已收录,有我的系列文章、材料和开源 Go 图书。

大家好,我是煎鱼。

最近金三银四,是面试的节令。在我的 Go 读者交换群里呈现了许多小伙伴在探讨本人面试过程中所遇到的一些 Go 面试题。

明天的配角,是大家在既有语言根底的状况下,学 Goroutine 时会容易纠结的一点。就是“过程、线程都有 ID,为什么 Goroutine 没有 GoroutineID?”。

这是为什么呢,怎么做那些跨协程解决呢?

GoroutineID 是什么

咱们要晓得,为什么大家会下意识的想去要 GoroutineID,上面援用 Go 语言圣经中的表述:

在大多数反对多线程的操作系统和程序语言中,以后的线程都有一个独特的身份(ID),并且这个身份信息能够以一个一般值的模式被很容易地获取到,典型的能够是一个 integer 或者指针值。这种状况下咱们做一个抽象化的 thread-local storage(线程本地存储,多线程编程中不心愿其它线程拜访的内容)就很容易,只须要以线程的 ID 作为 key 的一个 map 就能够解决问题,每一个线程以其 ID 就能从中获取到值,且和其它线程互不抵触。

也就在惯例的过程、线程中都有其 ID 的概念,咱们能够在程序中通过 ID 来获取其余过程、线程中的数据,甚至是传输数据。就像一把钥匙一样,有了他干啥都能够。

GoroutineID 的概念也是相似的,也就是协程的 ID。咱们下意识的就冀望通过协程 ID 来进行跨协程的操作。

但,在 Go 语言中 GoroutineID 并没有显式获取的方法,这就要打个大大的纳闷了。

为什么没有 GoroutineID

为什么在 Go 语言中没有 GoroutineID 呢,是从一开始就没有的,还是,这样子设计的起因是什么呢?

其实 Go 语言在以前是有裸露办法去获取 GoroutineID 的,但在 Go1.4 后就把该办法给暗藏起来了,不倡议大家应用。

也就是明面上没有 GoroutineID,是一个无意而为之的行为。起因是:依据以往的教训,认为 thread-local storage 存在被滥用的可能性,且带来许多不必要的复杂度

简略来讲,Andrew Gerrand 的答复是”thread-local storage 的老本远远超过了它们的收益。它们只是不适宜 Go 语言。”

潜在的问题

  • 当 Goroutine 隐没时:

    • 它的 Goroutine 本地存储将不会被 GC 化。(你能够失去 goid 的以后的 Goroutine,但你不能失去所有运行的 Goroutine 的列表)
  • 如果处理程序本人产生了新的 Goroutine 怎么办?

    • 新的 Goroutine 失去了对既有的 Goroutine 本地存储。尽管你能够保障本人的代码不会产生其余的 Goroutine。
    • 一般来说,你不能确保规范库或任何第三方代码不会这样做。
  • Go 应用程序的复杂度和心智累赘等回升。

滥用的场景

有一个对外提供 HTTP 服务的 Go 利用,也就是 Web Server。Go HTTP Server 都是采取每次申请新起一个协程的形式。

假如能够通过 GoroutineID 进行跨协程操纵,那么就有可能呈现我的 Goroutine,不肯定是由“我”本人决定的。可能其余正在解决的 GoroutineB 轻轻摸摸的改了我这个 GoroutineA 的行为。

这就有可能导致一个劫难问题,就是出问题时,你不晓得是谁动了你的奶酪。查起问题来几乎就是一个劫难。

若是本人保护的模块分明还起码晓得这事,假如你的前共事刚好到职了,你又在相熟代码,一出问题。这锅那是死死的扣在了你的头上了。

如何获取 GoroutineID

刚刚咱们提到是在明面上把 GoroutineID 给暗藏了,那暗面呢,是不是有其余方法能够获取到?

答案是:能够的。

通过骇客代码的形式能够获取到。在 Go 语言的规范库 http/2 的 gotrack 中,就有提供如下获取办法:

func main() {go func() {fmt.Println("脑子进煎鱼了的 GoroutineID:", curGoroutineID())
    }()

    time.Sleep(time.Second)
}

func curGoroutineID() uint64 {bp := littleBuf.Get().(*[]byte)
    defer littleBuf.Put(bp)
    b := *bp
    b = b[:runtime.Stack(b, false)]
    // Parse the 4707 out of "goroutine 4707 ["
    b = bytes.TrimPrefix(b, goroutineSpace)
    i := bytes.IndexByte(b, ' ')
    if i < 0 {panic(fmt.Sprintf("No space found in %q", b))
    }
    b = b[:i]
    n, err := parseUintBytes(b, 10, 64)
    if err != nil {panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err))
    }
    return n
}

var littleBuf = sync.Pool{New: func() interface{} {buf := make([]byte, 64)
        return &buf
    },
}

var goroutineSpace = []byte("goroutine")

输入后果为:

脑子进煎鱼了的 GoroutineID:18

联合 curGoroutineID 办法来看,能够通过对 Go 运行时的剖析,也就是 runtime.Stack 从而失去 GoroutineID。

其作用,更多的是对进行跟踪和调试作用居多。因为官网并没有依据 GoroutineID 提供一系列跨协程操纵的办法。

也有如下开源库能够用于获取 GoroutineID(不过均多年未保护了):

  • davecheney/junk
  • jtolio/gls
  • tylerstillwater/gls

Go 团队的 Dave Cheney 对其所开源的 GoroutineID 库,评估:“If you use this package, you will go straight to hell.”:

也就是“如果你应用这个包,你会间接下天堂。“,十分猛了,深深地劝退大家应用。

日常在哪里常见

如果大家常常做救火队长,去排查 Go 工程中的问题,例如:谬误堆栈信息、PProf 性能剖析等调试信息。

因而常常看到 GoroutineID,也就是“goroutine #### […]”。

咱们所看到的 #### 就是实在的 GoroutineID,残余的信息就是一些堆栈跟踪和谬误形容了。

应该应用 GoroutineID 吗?

从后果来看,必定是不举荐应用 GoroutineID 了。毕竟没有什么特地的益处,Go 团队也是拥护的。

所以个别都会间接答复”无奈获取 GoroutineID“,该当跟从语言设计理念,应用 Share Memory By Communicating 来实现跨协程的操纵会更正当。

总结

明天这篇文章咱们依据 GoroutineID 的历史,作用,起因,骇客办法进行了逐个梳理,摸索了下外面到底为何物。

过程、线程、协程的比照是一个面试中常被拿进去问的话题,而 GoroutineID 就是其中一点,这波及到整个全局上的设计思考。

你又是否遇到过 GoroutineID 应用和疑难的场景呢,欢送大家一起留言探讨。

若有任何疑难欢送评论区反馈和交换,最好的关系是相互成就 ,各位的 点赞 就是煎鱼创作的最大能源,感激反对。

文章继续更新,能够微信搜【脑子进煎鱼了】浏览,回复【000】有我筹备的一线大厂面试算法题解和材料;本文 GitHub github.com/eddycjy/blog 已收录,欢送 Star 催更。

退出移动版