一、野生goroutine的问题 引言: 毋庸置疑,golang原生的goroutine极大降低了程序员使用并发编程的代价,程序员们不需要再去关心如何实现接口、如何继承Thread类等多余的操作,只需要简简单单的go, 就可以开启一个并发的协程。但这种简单的使用方式同时也带来一些问题,这些goroutine不再受我们控制了,它们在运行时可能会发生任何错误或意外,而我们无法得知或去处理这些意外。我们将启动后不再受主进程控制的goroutine称为野生goroutine,本节将介绍野生goroutine存在的一些问题并介绍一些简单的解决方法。
1. goroutine中panic无法恢复 正常的函数中panic的recover
import ( "fmt" ) func main(){ defer func(){ if err := recover(); err != nil{ fmt.Println(err) } }() var bar = []int{1} fmt.Println(bar[1]) } reflect: slice index out of range goroutine中panic的恢复
func main(){ defer func(){ if err := recover(); err != nil { // 在这里使用recover(),不能捕获panic fmt.Println("catch you, bad guy") } }() go func(){ fmt.Println("I'm in a goroutine") panic("come to catch me") }() time....
1. 线程 在操作系统中,进程是分配资源的基本单位,但当进程作为调度的基本单位时,会造成较大的开销,频繁的进程调度将消耗大量时间。因此引出了线程:线程是处理器调度的基本单位,线程只拥有很小的运行时必要的资源。一个进程可拥有多个线程,同一个进程中的所有线程共享进程获得的主存空间和资源。 线程的实现
有些系统同时支持用户线程和内核线程,由此产生了不同的多线程模型,即实现用户级线程 和内核级线程的连接方式:多对一模型、一对一模型、多对多模型。
2. goroutine 在Go语言中,每一个并发的执行单元叫作一个goroutine,是一种轻量级的线程。
3. 线程与goroutine的区别 运行时栈的大小
每个系统级线程都会有一个固定大小的栈(一般为2MB),主要用于保存函数递归调用时参数和局部变量。这造成了两个问题: 对于某些需要很小的栈空间的线程来说是一个巨大的浪费 对于少数需要巨大栈空间的线程来说又面临栈溢出的风险 goroutine会以一个很小的栈启动(2KB或4KB),当遇到深度递归时导致当前栈空间不足,会根据需要动态的伸缩栈的大小。 调度
go的运行时还包括了其自己的调度器,可以在n个操作系统线程上多工调度m个goroutine(类似于多线程模型中的多对多模型)。 go调度器的工作和内核的调度时相似的,但是这个调度器只关注单独的go程序中的goroutine。 goroutinie采用的是半抢占式的协作调度,只有当当前goroutine发生阻塞时才会导致调度。 这种调度发生在用户态,调度器会根据具体函数只保存必要的寄存器,切换的代价比系统线程要低得多。 创建和销毁
Thread 创建和销毀都会有巨大的消耗,因为要和操作系统打交道,是内核级的,通常解决的办法就是线程池。- goroutine 因为是由 Go runtime 负责管理的,创建和销毁的消耗非常小,是用户级。