如何优雅的控制goroutine的数量

原创
2016/05/27 15:44
阅读数 3W

1,为什么要控制goroutine的数量?     goroutine固然好,但是数量太多了,往往会带来很多麻烦,比如耗尽系统资源导致程序崩溃,或者CPU使用率过高导致系统忙不过来。比如:

for i:=0; i < 10000; i++ {
    go work()
}

2,用什么方法控制goroutine的数量? 要在每一次执行go之前判断goroutine的数量,如果数量超了,就要阻塞go的执行。第一时间想到的就是使用通道。每次执行的go之前向通道写入值,直到通道满的时候就阻塞了,如下:

var ch chan int

func work() {
    //do something
    <-ch
}

func main() {
    ch = make(chan int, 10)
    for i:=0; i < 10000; i++ {
       ch <- 1
       go work()
    }
}

这样每次同时运行的goroutine就被限制为10个了。但是新的问题出现了,因为并不是所有的goroutine都执行完了,在main函数退出之后,还有一些goroutine没有执行完就被强制结束了。这个时候我们就需要用到sync.WaitGroup。使用WaitGroup等待所有的goroutine退出。如下:

var wg *sync.WaitGroup

func work() {
    defer wg.Done()
    //do something
}

func main() {
    wg = &sync.WaitGroup{}
    for i:=0; i < 10000; i++ {
       wg.Add(1)
       go work()
    }
    wg.Wait()//等待所有goroutine退出
}

3,优雅的使用并控制goroutine的数量 综上所述,我们封装一下,代码如下:

package gpool

import (
	"sync"
)

type pool struct {
	queue chan int
	wg    *sync.WaitGroup
}

func New(size int) *pool {
	if size <= 0 {
		size = 1
	}
	return &pool{
		queue: make(chan int, size),
		wg:    &sync.WaitGroup{},
	}
}

func (p *pool) Add(delta int) {
	for i := 0; i < delta; i++ {
		p.queue <- 1
	}
	for i := 0; i > delta; i-- {
		<-p.queue
	}
	p.wg.Add(delta)
}

func (p *pool) Done() {
	<-p.queue
	p.wg.Done()
}

func (p *pool) Wait() {
	p.wg.Wait()
}

来段测试代码:

package gpool_test

import (
	"runtime"
	"testing"
	"time"
	"gpool"
)

func Test_Example(t *testing.T) {
	pool := gpool.New(100)
	println(runtime.NumGoroutine())
	for i := 0; i < 1000; i++ {
		pool.Add(1)
		go func() {
			time.Sleep(time.Second)
			println(runtime.NumGoroutine())
			pool.Done()
		}()
	}
	pool.Wait()
	println(runtime.NumGoroutine())
}

good job,Over~

展开阅读全文
打赏
5
75 收藏
分享
加载中
for i := 0; i > delta; i-- {
<-p.queue
}
这段代码有什么作用?
04/17 11:43
回复
举报
for i := 0; i > delta; i-- {
    <-p.queue
  }
这段代码有什么作用?
2019/09/08 18:55
回复
举报
那怎么得出"最合适"的goroutine数量呢
2017/07/17 12:00
回复
举报
borey博主

引用来自“sheepbao”的评论

最优雅的是用channel缓冲

我只想知道为什么标准库sync.WaitGroup不提供这个功能。难道是一次只做一件事么。
2016/05/28 21:41
回复
举报
最优雅的是用channel缓冲
2016/05/28 21:06
回复
举报
2可以的
2016/05/28 14:34
回复
举报
borey博主

引用来自“borey”的评论

引用来自“lubia”的评论

很多时候的需求是有1万个请求,但我只能开100个goroutine,需要控制同时运行的数量,怎么破?

这个就可以啊,New(100)就是最多开100个routine

同时最多开100个,一个routine结束就可以新开一个。接受请求的时候就直接开就可以了。buffer chan满了就会阻塞。
2016/05/28 13:21
回复
举报
borey博主

引用来自“lubia”的评论

很多时候的需求是有1万个请求,但我只能开100个goroutine,需要控制同时运行的数量,怎么破?

这个就可以啊,New(100)就是最多开100个routine
2016/05/28 12:44
回复
举报
很多时候的需求是有1万个请求,但我只能开100个goroutine,需要控制同时运行的数量,怎么破?
2016/05/28 12:00
回复
举报
borey博主

引用来自“hell0cat”的评论

纯channel可以实现的,不需要再依赖sync同步机制,比如我都是这样用并发机制的:
http://www.oschina.net/code/snippet_170216_25349
可以对你的这个例子用我的方法改造一下,很简单的测试不同数量的goroutine的效果如何
2016/05/28 09:37
回复
举报
更多评论
打赏
13 评论
75 收藏
5
分享
返回顶部
顶部