在Golang中,Goroutine虽然很好,但是数量太多了,往往会带来很多麻烦,比如耗尽系统资源导致程序崩溃,或者CPU使用率过高导致系统忙不过来。
所以我们可以限制下Goroutine的数量,这样就需要在每一次执行go之前判断goroutine的数量,如果数量超了,就要阻塞go的执行。
所以通常我们第一时间想到的就是使用通道。每次执行的go之前向通道写入值,直到通道满的时候就阻塞了,
~~~go
package main
import "fmt"
var ch chan int
func elegance(){
<-ch
fmt.Println("the ch value receive",ch)
}
func main(){
ch = make(chan int,5)
for i:=0;i<10;i++{
ch <-1
fmt.Println("the ch value send",ch)
go elegance()
fmt.Println("the result i",i)
}
}
~~~
运行:
~~~go
> go run goroutine.go
the ch value send 0xc00009c000
the result i 0
the ch value send 0xc00009c000
the result i 1
the ch value send 0xc00009c000
the result i 2
the ch value send 0xc00009c000
the result i 3
the ch value send 0xc00009c000
the result i 4
the ch value send 0xc00009c000
the result i 5
the ch value send 0xc00009c000
the ch value receive 0xc00009c000
the result i 6
the ch value receive 0xc00009c000
the ch value send 0xc00009c000
the result i 7
the ch value send 0xc00009c000
the result i 8
the ch value send 0xc00009c000
the result i 9
the ch value send 0xc00009c000
the ch value receive 0xc00009c000
the ch value receive 0xc00009c000
the ch value receive 0xc00009c000
the result i 10
the ch value send 0xc00009c000
the result i 11
the ch value send 0xc00009c000
the result i 12
the ch value send 0xc00009c000
the result i 13
the ch value send 0xc00009c000
the ch value receive 0xc00009c000
the ch value receive 0xc00009c000
the ch value receive 0xc00009c000
the ch value receive 0xc00009c000
the result i 14
the ch value receive 0xc00009c000
~~~
~~~
> go run goroutine.go
the ch value send 0xc00007e000
the result i 0
the ch value send 0xc00007e000
the result i 1
the ch value send 0xc00007e000
the result i 2
the ch value send 0xc00007e000
the result i 3
the ch value send 0xc00007e000
the ch value receive 0xc00007e000
the result i 4
the ch value send 0xc00007e000
the ch value receive 0xc00007e000
the result i 5
the ch value send 0xc00007e000
the ch value receive 0xc00007e000
the result i 6
the ch value send 0xc00007e000
the result i 7
the ch value send 0xc00007e000
the ch value receive 0xc00007e000
the ch value receive 0xc00007e000
the ch value receive 0xc00007e000
the result i 8
the ch value send 0xc00007e000
the result i 9
~~~
这样每次同时运行的goroutine就被限制为5个了。但是新的问题于是就出现了,因为并不是所有的goroutine都执行完了,在main函数退出之后,还有一些goroutine没有执行完就被强制结束了。这个时候我们就需要用到sync.WaitGroup。使用WaitGroup等待所有的goroutine退出。
~~~go
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(){
// 这里限制5个并发
pool := NewPool(5)
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 NumGoroutine continue is:",runtime.NumGoroutine())
pool.Done()
}(i)
}
pool.Wait()
fmt.Println("the NumGoroutine done is:",runtime.NumGoroutine())
}
~~~
运行:
~~~go
the NumGoroutine begin is: 1
the NumGoroutine continue is: 6
the NumGoroutine continue is: 7
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 6
the NumGoroutine continue is: 3
the NumGoroutine continue is: 2
the NumGoroutine done is: 1
~~~
其中,Go的`GOMAXPROCS`默认值已经设置为CPU的核数, 这里允许我们的Go程序充分使用机器的每一个CPU,最大程度的提高我们程序的并发性能。`runtime.NumGoroutine`函数在被调用后,会返回系统中的处于特定状态的Goroutine的数量。这里的特指是指`Grunnable\Gruning\Gsyscall\Gwaition`。处于这些状态的Groutine即被看做是活跃的或者说正在被调度。
这里需要注意下:垃圾回收所在Groutine的状态也处于这个范围内的话,也会被纳入该计数器。
- Golang基础
- Go中new与make的区别
- Golang中除了加Mutex锁以外还有哪些方式安全读写共享变量
- 无缓冲Chan的发送和接收是否同步
- Golang并发机制以及它所使用的CSP并发模型.
- Golang中常用的并发模型
- Go中对nil的Slice和空Slice的处理是一致的吗
- 协程和线程和进程的区别
- Golang的内存模型中为什么小对象多了会造成GC压力
- Go中数据竞争问题怎么解决
- 什么是channel,为什么它可以做到线程安全
- Golang垃圾回收算法
- GC的触发条件
- Go的GPM如何调度
- 并发编程概念是什么
- Go语言的栈空间管理是怎么样的
- Goroutine和Channel的作用分别是什么
- 怎么查看Goroutine的数量
- Go中的锁有哪些
- 怎么限制Goroutine的数量
- Channel是同步的还是异步的
- Goroutine和线程的区别
- Go的Struct能不能比较
- Go的defer原理是什么
- Go的select可以用于什么
- Context包的用途是什么
- Go主协程如何等其余协程完再操作
- Go的Slice如何扩容
- Go中的map如何实现顺序读取
- Go中CAS是怎么回事
- Go中的逃逸分析是什么
- Go值接收者和指针接收者的区别
- Go的对象在内存中是怎样分配的
- 栈的内存是怎么分配的
- 堆内存管理怎么分配的
- 在Go函数中为什么会发生内存泄露
- G0的作用
- Go中的锁如何实现
- Go中的channel的实现
- 栈的内存是怎么分配的2
- 堆内存管理怎么分配的2
- Go中的map的实现
- Go中的http包的实现原理
- Goroutine发生了泄漏如何检测
- Go函数返回局部变量的指针是否安全
- Go中两个Nil可能不相等吗
- Goroutine和KernelThread之间是什么关系
- 为何GPM调度要有P
- 如何在goroutine执行一半就退出协程
- Mysql基础
- Mysql索引用的是什么算法
- Mysql事务的基本要素
- Mysql的存储引擎
- Mysql事务隔离级别
- Mysql高可用方案有哪些
- Mysql中utf8和utf8mb4区别
- Mysql中乐观锁和悲观锁区别
- Mysql索引主要是哪些
- Mysql联合索引最左匹配原则
- 聚簇索引和非聚簇索引区别
- 如何查询一个字段是否命中了索引
- Mysql中查询数据什么情况下不会命中索引
- Mysql中的MVCC是什么
- Mvcc和Redolog和Undolog以及Binlog有什么不同
- Mysql读写分离以及主从同步
- InnoDB的关键特性
- Mysql如何保证一致性和持久性
- 为什么选择B+树作为索引结构
- InnoDB的行锁模式
- 哈希(hash)比树(tree)更快,索引结构为什么要设计成树型
- 为什么索引的key长度不能太长
- Mysql的数据如何恢复到任意时间点
- Mysql为什么加了索引可以加快查询
- Explain命令有什么用
- Redis基础
- Redis的数据结构及使用场景
- Redis持久化的几种方式
- Redis的LRU具体实现
- 单线程的Redis为什么快
- Redis的数据过期策略
- 如何解决Redis缓存雪崩问题
- 如何解决Redis缓存穿透问题
- Redis并发竞争key如何解决
- Redis的主从模式和哨兵模式和集群模式区别
- Redis有序集合zset底层怎么实现的
- 跳表的查询过程是怎么样的,查询和插入的时间复杂度
- 网络协议基础
- TCP和UDP有什么区别
- TCP中三次握手和四次挥手
- TCP的LISTEN状态是什么
- 常见的HTTP状态码有哪些
- 301和302有什么区别
- 504和500有什么区别
- HTTPS和HTTP有什么区别
- Quic有什么优点相比Http2
- Grpc的优缺点
- Get和Post区别
- Unicode和ASCII以及Utf8的区别
- Cookie与Session异同
- Client如何实现长连接
- Http1和Http2和Grpc之间的区别是什么
- Tcp中的拆包和粘包是怎么回事
- TFO的原理是什么
- TIME_WAIT的作用
- 网络的性能指标有哪些