通过channel限制goroutine并发数量

在Golang中,Goroutine虽然很好,但是数量太多了,往往会带来很多麻烦,比如耗尽系统资源导致程序崩溃,或者CPU使用率过高导致系统忙不过来。

所以我们可以限制下Goroutine的数量,这样就需要在每一次执行go之前判断goroutine的数量,如果数量超了,就要阻塞go的执行。

所以通常我们第一时间想到的就是使用通道。每次执行的go之前向通道写入值,直到通道满的时候就阻塞了,

package main

import "fmt"

var ch chan  int

func elegance(){
	v := <-ch
	fmt.Println("the ch value receive",v)
}

func main(){
	ch = make(chan int,5)
	for i:=0;i<10;i++{
		ch <- i
		fmt.Println("the ch value send",ch)
		go elegance()
		fmt.Println("the result i",i)
	}
}
the ch value send 0xc000088000
the result i 0
the ch value send 0xc000088000
the result i 1
the ch value send 0xc000088000
the ch value receive 0
the ch value receive 1
the result i 2
the ch value send 0xc000088000
the result i 3
the ch value send 0xc000088000
the ch value receive 3
the ch value receive 2
the result i 4
the ch value send 0xc000088000
the result i 5
the ch value send 0xc000088000
the result i 6
the ch value send 0xc000088000
the result i 7
the ch value send 0xc000088000
the result i 8
the ch value send 0xc000088000
the result i 9

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

package main

import (
	"fmt"
	"runtime"
	"sync"
	"time"
)

// Pool Goroutine Pool
type Pool struct {
	queue chan int
	wg    *sync.WaitGroup
}

// New 新建一个协程池
func NewPool(size int) *Pool {
	if size <= 0 {
		size = 1
	}
	return &Pool{
		queue: make(chan int, size),
		wg:    &sync.WaitGroup{},
	}
}

// Add 新增一个执行
func (p *Pool) Add(delta int) {
	// delta为正数就添加
	for i := 0; i < delta; i++ {
		p.queue <- 1
	}
	// delta为负数就减少
	for i := 0; i > delta; i-- {
		<-p.queue
	}
	p.wg.Add(delta)
}

// Done 执行完成减一
func (p *Pool) Done() {
	<-p.queue
	p.wg.Done()
}

// Wait 等待Goroutine执行完毕
func (p *Pool) Wait() {
	p.wg.Wait()
}

func main() {
	// 这里限制为CPU内核数
	cores := runtime.NumCPU()
	pool := NewPool(cores)
	fmt.Println("the NumGoroutine begin is:", runtime.NumGoroutine())
	for i := 0; i < 20; i++ {
		pool.Add(1)
		go func(i int) {
			time.Sleep(time.Second)
			fmt.Println("the currentNum is:", i)
			fmt.Println("the NumGoroutine continue is:", runtime.NumGoroutine())
			pool.Done()
		}(i)
	}
	pool.Wait()
	fmt.Println("the NumGoroutine done is:", runtime.NumGoroutine())
}

运行

the NumGoroutine begin is: 1
the currentNum is: 1
the NumGoroutine continue is: 5
the currentNum is: 0
the NumGoroutine continue is: 5
the currentNum is: 2
the NumGoroutine continue is: 5
the currentNum is: 3
the NumGoroutine continue is: 5
the currentNum is: 6
the NumGoroutine continue is: 5
the currentNum is: 7
the NumGoroutine continue is: 5
the currentNum is: 5
the NumGoroutine continue is: 5
the currentNum is: 4
the NumGoroutine continue is: 5
the currentNum is: 9
the NumGoroutine continue is: 5
the currentNum is: 10
the NumGoroutine continue is: 5
the currentNum is: 11
the NumGoroutine continue is: 5
the currentNum is: 8
the NumGoroutine continue is: 5
the currentNum is: 13
the NumGoroutine continue is: 5
the currentNum is: 14
the NumGoroutine continue is: 5
the currentNum is: 15
the currentNum is: 12
the NumGoroutine continue is: 5
the NumGoroutine continue is: 5
the currentNum is: 18
the NumGoroutine continue is: 5
the currentNum is: 17
the NumGoroutine continue is: 4
the currentNum is: 16
the NumGoroutine continue is: 3
the currentNum is: 19
the NumGoroutine continue is: 2
the NumGoroutine done is: 1

其中,Go的GOMAXPROCS默认值已经设置为CPU的核数, 这里允许我们的Go程序充分使用机器的每一个CPU,最大程度的提高我们程序的并发性能。runtime.NumGoroutine函数在被调用后,会返回系统中的处于特定状态的Goroutine的数量。这里的特指是指 Grunnable\Gruning\Gsyscall\Gwaition。处于这些状态的Groutine即被看做是活跃的或者说正在被调度。

这里需要注意下:垃圾回收所在Groutine的状态也处于这个范围内的话,也会被纳入该计数器。

文章来源于:http://wearygods.online/articles/2021/04/19/1618823886966.html#toc_h4_18

你可能感兴趣的:(GO语言,golang,开发语言,后端)