为什么需要控制
1 | func main() { |
过高的并发会将系统资源消耗殆尽,导致程序运行最终panic,关键的报错信息如下:panic: too many concurrent operations on a single file or socket (max 1048575)
- 导致出现上述错误的原因是源自fmt.Printf函数输出到标准输出,标准输出也可以视为文件,总之就是系统的资源被耗尽了。
- 就算注释掉fmt.Printf函数,也会因为内存不足而最终崩溃。
如何解决
解决的主要方式就是限制并发的协程数量。
使用带缓冲的channel进行控制
1 | func main() { |
- 创建一个缓冲区大小为3的channel,在没有被接收的情况下,最多发送3个消息则被阻塞
- 开启协程前,调用
ch <- struct{}{}
,若缓冲区满则阻塞- 协程任务结束,调用
<-ch
释放缓冲区
利用第三方库
目前有很多第三方库实现了协程池,可以很方便的用来控制协程的并发数量,如Jeffail/tunny
,panjf2000/ants
,以tunny
举例如下:
1 | func main() { |
tunny.NewFunc(3, f)
第一个参数是协程池的大小(poolSize),第二个参数是协程运行的函数(worker)pool.Process(i)
将参数i传递给协程池定义好的worker处理pool.Close()
关闭协程池
调整系统资源上限
ulimit
有些情况下,即使我们有效的限制了协程的并发数量,但是仍旧出现某一类资源不足的问题,例如:
- too many open files
- out of memory
操作系统通常会限制同时打开文件数量,栈空间大小等,ulimit -a
可以看到系统的当前设置:
1 | [root@master ~]# ulimit -a |
进而进行按需调整即可。
虚拟内存virtual memory
虚拟内存是一项非常常见的技术,当内存不足时,将磁盘映射为内存使用,比如linux下的交换分区(swap space)。在linux上创建并使用交换分区是一件非常简单的事情:
1 | sudo fallocate -l 20G /mnt/.swapfile # 创建 20G 空文件 |
关闭交换分区也非常简单:
1 | sudo swapoff /mnt/.swapfile |
磁盘的 I/O 读写性能和内存条相差是非常大的,例如 DDR3 的内存条读写速率很容易达到 20GB/s,但是 SSD 固态硬盘的读写性能通常只能达到 0.5GB/s,相差 40倍之多。因此,使用虚拟内存技术将硬盘映射为内存使用,显然会对性能产生一定的影响。如果应用程序只是在较短的时间内需要较大的内存,那么虚拟内存能够有效避免 out of memory 的问题。如果应用程序长期高频度读写大量内存,那么虚拟内存对性能的影响就比较明显了。