Go语言中的goroutine
虽然相对于系统线程来说比较轻量级(初始栈大小仅2KB
),(并且支持动态扩容),而正常采用java,c++等语言启用的线程一般都是内核态的占用的内存资源一般在4m左右,而假设我们的服务器CPU内存为4G,那么很明显才用的内核态线程的并发总数量也就是1024个,相反查看一下Go语言的协程则可以达到4*1024*1024/2=200w.这么一看就明白了为什么Go语言天生支持高并发。
但是在高并发量下的goroutine
频繁创建和销毁对于性能损耗以及GC
来说压力也不小。充分将goroutine
复用,减少goroutine
的创建/销毁的性能损耗,这便是grpool
对goroutine
进行池化封装的目的。例如,针对于100W
个执行任务,使用goroutine
的话需要不停创建并销毁100W
个goroutine
,而使用grpool
也许底层只需要几万个goroutine
便能充分复用地执行完成所有任务。
异步执行时并不会保证按照函数注册时的顺序执行
对于异步线程/协程来讲,函数进行异步执行注册时,该函数并未真正开始执行(注册时只在goroutine的栈中保存了变量i的内存地址),
而一旦开始执行时函数才会去读取变量i的值,而这个时候变量i的值已经自增到了10。
清楚原因之后,改进方案也很简单了,就是在注册异步执行函数的时候,把当时变量i的值也一并传递获取;
或者把当前变量i的值赋值给一个不会改变的临时变量,在函数中使用该临时变量而不是直接使用变量i。
// 在注册异步执行函数的时候,把当时变量i的值也一并传递获取; wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func(v int) { fmt.Println(v) wg.Done() }(i) } wg.Wait() //0 //9 //7 //8 //5 //2 //1 //3 //6 //4
//把当前变量i的值赋值给一个不会改变的临时变量,在函数中使用该临时变量而不是直接使用变量i //采用临时变量的形式来传递当前变量i的值 wg3 := sync.WaitGroup{} for i := 0; i < 10; i++ { wg3.Add(1) a := i // 定义局部临时变量 go func() { fmt.Println(a) wg3.Done() }() } wg3.Wait() //9 //4 //0 //1 //2 //3 //6 //5 //7 //8
错误方式
// 相对于协程,i相当于全局变量 wg2 := sync.WaitGroup{} for i := 0; i < 10; i++ { wg2.Add(1) go func() { fmt.Println(i) wg2.Done() }() } wg2.Wait() //10 //10 //10 //10 //10 //10 //10 //10 //10 //10
//全局变量不可行 wg4 := sync.WaitGroup{} var a int // 定义全局变量 for i := 0; i < 10; i++ { wg4.Add(1) a = i // go func() { fmt.Println(a) wg4.Done() }() } wg4.Wait() //9 //9 //9 //9 //9 //9 //9 //9 //9 //9