GO并发过高导致程序崩溃如何解决

#作者:曹付江

文章目录

  • 1.并发过高导致程序崩溃
  • 2. 如何解决
    • 2.1 利用 channel 的缓存区
    • 2.2 利用第三方库
  • 3 调整系统资源的上限
    • 3.1 ulimit
    • 3.2 虚拟内存(virtual memory)

1.并发过高导致程序崩溃

看一个非常简单的例子:

func main() {var wg sync.WaitGroupfor i := 0; i < math.MaxInt32; i++ {wg.Add(1)go func(i int) {defer wg.Done()fmt.Println(i)time.Sleep(time.Second)}(i)}wg.Wait()
}

这个例子实现了 math.MaxInt32 个协程的并发,约 2^31 = 2 亿个,每个协程内部几乎没有做什么事情。正常的情况下呢,这个程序会乱序输出 1 -> 2^31 个数字。
那实际运行的结果是怎么样的呢?

$ go run main.go
...
150577
150578
panic: too many concurrent operations on a single file or socket (max 1048575)goroutine 1199236 [running]:
internal/poll.(*fdMutex).rwlock(0xc0000620c0, 0x0, 0xc0000781b0)/usr/local/go/src/internal/poll/fd_mutex.go:147 +0x13f
internal/poll.(*FD).writeLock(...)/usr/local/go/src/internal/poll/fd_mutex.go:239
internal/poll.(*FD).Write(0xc0000620c0, 0xc125ccd6e0, 0x11, 0x20, 0x0, 0x0, 0x0)/usr/local/go/src/internal/poll/fd_unix.go:255 +0x5e
fmt.Fprintf(0x10ed3e0, 0xc00000e018, 0x10d3024, 0xc, 0xc0e69b87b0, 0x1, 0x1, 0x11, 0x0, 0x0)/usr/local/go/src/fmt/print.go:205 +0xa5
fmt.Printf(...)/usr/local/go/src/fmt/print.go:213
main.main.func1(0xc0000180b0, 0x124c31)

… 运行的结果是程序直接崩溃了,关键的报错信息是:
panic: too many concurrent operations on a single file or socket (max 1048575)

对单个 file/socket 的并发操作个数超过了系统上限,这个报错是 fmt.Printf 函数引起的,fmt.Printf 将格式化后的字符串打印到屏幕,即标准输出。在 linux 系统中,标准输出也可以视为文件,内核(kernel)利用文件描述符(file descriptor)来访问文件,标准输出的文件描述符为 1,错误输出文件描述符为 2,标准输入的文件描述符为 0。
简而言之,系统的资源被耗尽了。
那如果我们将 fmt.Printf 这行代码去掉呢?那程序很可能会因为内存不足而崩溃。这一点更好理解,每个协程至少需要消耗 2KB 的空间,那么假设计算机的内存是 2GB,那么至多允许 2GB/2KB = 1M 个协程同时存在。那如果协程中还存在着其他需要分配内存的操作,那么允许并发执行的协程将会数量级地减少。

2. 如何解决

不同的应用程序,消耗的资源是不一样的。比较推荐的方式的是:应用程序来主动限制并发的协程数量

2.1 利用 channel 的缓存区

// main_chan.go
func main() {var wg sync.WaitGroupch := make(chan struct{}, 3)for i := 0; i < 10; i++ {ch <- struct{}{}wg.Add(1)go func(i int) {defer wg.Done()log.Println(i)time.Sleep(time.Second)<-ch}(i)}wg.Wait()
}
  • make(chan struct{}, 3) 创建缓冲区大小为 3 的 channel,在没有被接收的情况下,至多发送 3 个消息则被阻塞。
  • 开启协程前,调用 ch <- struct{}{},若缓存区满,则阻塞。
  • 协程任务结束,调用 <-ch 释放缓冲区。
  • sync.WaitGroup 并不是必须的,例如 http 服务,每个请求天然是并发的,此时使用 channel 控制并发处理的任务数量,就不需要 sync.WaitGroup。
    运行结果如下:
$ go run main_chan.go
2020/12/21 00:48:28 2
2020/12/21 00:48:28 0
2020/12/21 00:48:28 1
2020/12/21 00:48:29 3
2020/12/21 00:48:29 4
2020/12/21 00:48:29 5
2020/12/21 00:48:30 6
2020/12/21 00:48:30 7
2020/12/21 00:48:30 8
2020/12/21 00:48:31 9

从日志中可以很容易看到,每秒钟只并发执行了 3 个任务,达到了协程并发控制的目的。

2.2 利用第三方库

目前有很多第三方库实现了协程池,可以很方便地用来控制协程的并发数量,比较受欢迎的有:

  • Jeffail/tunny
  • panjf2000/ants
    以 tunny 举例:
package mainimport ("log""time""github.com/Jeffail/tunny"
)
func main() {pool := tunny.NewFunc(3, func(i interface{}) interface{} {log.Println(i)time.Sleep(time.Second)return nil})defer pool.Close()for i := 0; i < 10; i++ {go pool.Process(i)}time.Sleep(time.Second * 4)
}
  • tunny.NewFunc(3, f) 第一个参数是协程池的大小(poolSize),第二个参数是协程运行的函数(worker)。
  • pool.Process(i) 将参数 i 传递给协程池定义好的 worker 处理。
  • pool.Close() 关闭协程池。
    运行结果如下:
$ go run main_tunny.go
2020/12/21 01:00:21 6
2020/12/21 01:00:21 1
2020/12/21 01:00:21 3
2020/12/21 01:00:22 8
2020/12/21 01:00:22 4
2020/12/21 01:00:22 7
2020/12/21 01:00:23 5
2020/12/21 01:00:23 2
2020/12/21 01:00:23 0
2020/12/21 01:00:24 9

3 调整系统资源的上限

3.1 ulimit

有些场景下,即使我们有效地限制了协程的并发数量,但是仍旧出现了某一类资源不足的问题,例如:

  • too many open files
  • out of memory

例如分布式编译加速工具,需要解析 gcc 命令以及依赖的源文件和头文件,有些编译命令依赖的头文件可能有上百个,那这个时候即使我们将协程的并发数限制到 1000,也可能会超过进程运行时并发打开的文件句柄数量,但是分布式编译工具,仅将依赖的源文件和头文件分发到远端机器执行,并不会消耗本机的内存和 CPU 资源,因此1000个并发并不高,这种情况下,降低并发数会影响编译加速的效率,那能不能增加进程能同时打开的文件句柄数量呢?
操作系统通常会限制同时打开文件数量、栈空间大小等,ulimit -a 可以看到系统当前的设置:

$ ulimit -a
-t: cpu time (seconds)              unlimited
-f: file size (blocks)              unlimited
-d: data seg size (kbytes)          unlimited
-s: stack size (kbytes)             8192
-c: core file size (blocks)         0
-v: address space (kbytes)          unlimited
-l: locked-in-memory size (kbytes)  unlimited
-u: processes                       1418
-n: file descriptors                12800

我们可以使用 ulimit -n 999999,将同时打开的文件句柄数量调整为 999999 来解决这个问题,其他的参数也可以按需调整。

3.2 虚拟内存(virtual memory)

虚拟内存是一项非常常见的技术了,即在内存不足时,将磁盘映射为内存使用,比如 linux 下的交换分区(swap space)。
在 linux 上创建并使用交换分区是一件非常简单的事情:
sudo fallocate -l 20G /mnt/.swapfile # 创建 20G 空文件
sudo mkswap /mnt/.swapfile # 转换为交换分区文件
sudo chmod 600 /mnt/.swapfile # 修改权限为 600
sudo swapon /mnt/.swapfile # 激活交换分区
free -m # 查看当前内存使用情况(包括交换分区)
关闭交换分区也非常简单:

sudo swapoff /mnt/.swapfile
rm -rf /mnt/.swapfile

磁盘的 I/O 读写性能和内存条相差是非常大的,例如 DDR3 的内存条读写速率很容易达到 20GB/s,但是 SSD 固态硬盘的读写性能通常只能达到 0.5GB/s,相差 40倍之多。因此,使用虚拟内存技术将硬盘映射为内存使用,显然会对性能产生一定的影响。如果应用程序只是在较短的时间内需要较大的内存,那么虚拟内存能够有效避免 out of memory 的问题。如果应用程序长期高频度读写大量内存,那么虚拟内存对性能的影响就比较明显了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/82664.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux -- gdb/cgdb的认识和使用

预备知识 程序的发布⽅式有两种&#xff0c; debug 模式和 release 模式&#xff0c; Linux gcc/g 出来的⼆进制程 序&#xff0c;默认是 release 模式。 要使⽤gdb调试&#xff0c;必须在源代码⽣成⼆进制程序的时候, 加上 -g 选项&#xff0c;如果没有添加&#x…

window 显示驱动开发-Direct3D 呈现性能改进(四)

调用资源创建、映射和取消映射函数的行为更改 对于 WDDM 1.3 及更高版本驱动程序实现的这些函数&#xff0c;Direct3D 运行时为映射默认方案提供一组受限的输入值。 这些受限值仅适用于支持功能级别 11.1 及更高版本的驱动程序。 CreateResource (D3D11) 函数— 这些输入 D3…

3.python操作mysql数据库

前言&#xff1a;在现代应用程序中&#xff0c;数据库扮演者至关重要的角色。mysql是一个流行的关系型数据库管理系统&#xff0c;广泛应用于各种规模的应用中。在pytho中&#xff0c;我们可以通过连接库与mysql数据库进行交互&#xff0c;实现数据的增删改查操作。与此同时&am…

day023-网络基础与OSI七层模型

文章目录 1. 网络基础知识点1.1 网络中的单位1.2 查看实时网速&#xff1a;iftop1.3 交换机、路由器 2. 路由表2.1 查看路由表的命令2.2 路由追踪命令 3. 通用网站网络架构4. 局域网上网原理-NAT5. 虚拟机上网原理6. 虚拟机的网络模式6.1 NAT模式6.2 桥接模式6.3 仅主机模式 7.…

DeepSeek智能对话助手项目

目录&#xff1a; 1、效果图2、实现代码3、温度和TopK的作用对比 1、效果图 2、实现代码 # import gradio as gr# def reverse_text(text): # return text[::-1]# demogr.Interface(fnreverse_text,inputs"text",outputs"text")# demo.launch(share&q…

视觉中国:镜头下的中国发展图景

2025年5月下旬&#xff0c;从北国草原到江南水乡&#xff0c;从文化遗产到科技创新&#xff0c;中国大地上演着一幕幕生机勃勃的图景。河北张家口的沙狐幼崽与湿地生态和谐共生&#xff0c;湖北襄阳的茶园雕琢出诗意田园&#xff1b;北京殷商文创的活力、沈阳文物情景剧的创意&…

LabVIEW 中内存释放相关问题

在LabVIEW 编程领域&#xff0c;内存管理是一个关键且复杂的议题。我们常常关注 LabVIEW 如何将内存释放回操作系统&#xff08;OS&#xff09;&#xff0c;以及是否有方法确保在特定数据结构&#xff08;如队列、变体属性、动态数据引用 DVR 等&#xff09;销毁、删除或清空后…

基于正点原子阿波罗F429开发板的LWIP应用(4)——HTTP Server功能

说在开头 正点原子F429开发板主芯片采用的是STM32F429IGT6&#xff0c;网络PHY芯片采用的是LAN8720A(V1)和YT8512C(V2)&#xff0c;采用的是RMII连接&#xff0c;PHY_ADDR为0&#xff1b;在代码中将会对不同的芯片做出适配。 CubeMX版本&#xff1a;6.6.1&#xff1b; F4芯片组…

设计模式-结构型模式(详解)

适配器模式 将一个类的接口转换成客户端期望的另一个接口&#xff0c;解决接口不兼容问题。 适配器模式由四部分组成&#xff1a; 客户端&#xff1a;即需要使用目标接口的类 目标接口 需要适配的类&#xff0c;也就是已经存在好的功能&#xff0c;但客户端通过目标接口没办…

银河麒麟操作系统下载

产品试用申请国产操作系统、麒麟操作系统——麒麟软件官方网站 下载页面链接如上&#xff0c;申请试用即可。 申请试用填写后提交&#xff0c;界面就变成了这样&#xff0c;可以挑选适合自己的版本。 海思麒麟9006C版&#xff0c;如下&#xff1a; 本地下载&#xff1a;Kylin…

[CARLA系列--03]如何打包生成CARLA 0.9.15的非编辑版(地图的加载与卸载)

前两篇文章介绍了如何去安装可编辑版的CARLA 0.9.15&#xff0c;这个完整的工程文件实在是太大了&#xff0c;大概消耗了100个G的磁盘空间&#xff0c;当在进行一个CARLA项目的时候&#xff0c;不利于在每个开发电脑都去安装部署一套CARLA 0.9.15的源码&#xff0c;所以把自己这…

【机器学习基础】机器学习入门核心算法:朴素贝叶斯(Naive Bayes)

机器学习入门核心算法&#xff1a;朴素贝叶斯&#xff08;Naive Bayes&#xff09;&#xff09; 一、算法逻辑1.1 基本概念1.2 基本流程 二、算法原理与数学推导2.1 贝叶斯定理2.2 朴素贝叶斯分类器2.3 不同分布假设下的概率计算2.3.1 高斯朴素贝叶斯&#xff08;连续特征&…

云服务器系统盘满了,但是其他正常,是否可能是被攻击了

目录 问题背景分析解决系统盘满的问题解决结果 问题背景 今天登录我的云服务器看了眼&#xff0c;发现系统盘满了&#xff0c;但是其他正常 分析 1、首先要确认是否是被攻击&#xff1a; top / htop (安装&#xff1a;yum install htop 或 apt install htop)&#xff1a;…

双因子COX 交互 共线性 -spss

SPSS 简要界面操作步骤(针对双因子 COX 分析) 1. 数据准备 变量格式:确保数据已整理为以下格式(示例): 时间变量(如 Time_to_Recurrence)结局变量(如 Recurrence:1=复发,0=未复发)预测变量(CSPG4_HSCORE、FAM49B_Status 二分类变量)协变量(如 Lesion_Size、Pat…

【MySQL】第12节|MySQL 8.0 主从复制原理分析与实战(二)

一、组复制&#xff08;MGR&#xff09;核心概念 1. 定义与定位 目标&#xff1a;解决传统主从复制的单点故障、数据不一致问题&#xff0c;提供高可用、高扩展的分布式数据库方案。基于 GTID&#xff1a;依赖全局事务标识符&#xff08;GTID&#xff09;实现事务一致性&…

React 泛型组件:用TS来打造灵活的组件。

文章目录 前言一、什么是泛型组件&#xff1f;二、为什么需要泛型组件&#xff1f;三、如何在 React 中定义泛型组件&#xff1f;基础泛型组件示例使用泛型组件 四、泛型组件的高级用法带默认类型的泛型组件多个泛型参数 五、泛型组件的实际应用场景数据展示组件表单组件状态管…

如何手搓一个查询天气的mcp server

环境配置烦请移步上一篇博客 这里直接步入主题&#xff0c;天气查询的api用的是openweather&#xff0c;免费注册就可以使用了 每天1000次内使用时免费的&#xff0c;大概的api 如下 https://api.openweathermap.org/data/2.5/weather?qBeijing,cn&APPID注册后可以拿到一个…

深入解析计算机网络核心协议:ARP、DHCP、DNS与HTTP

文章目录 一、ARP&#xff08;地址解析协议&#xff09;1.1 定义与功能1.2 工作原理1.3 应用场景1.4 安全风险与防御 二、DHCP&#xff08;动态主机配置协议&#xff09;2.1 定义与功能2.2 工作原理2.3 应用场景2.4 优缺点与安全建议 三、DNS&#xff08;域名系统&#xff09;3…

《Java 单例模式:从类加载机制到高并发设计的深度技术剖析》

【作者简介】“琢磨先生”--资深系统架构师、985高校计算机硕士&#xff0c;长期从事大中型软件开发和技术研究&#xff0c;每天分享Java硬核知识和主流工程技术&#xff0c;欢迎点赞收藏&#xff01; 一、单例模式的核心概念与设计目标 在软件开发中&#xff0c;我们经常会遇…

NL2SQL代表,Vanna

Vanna 核心功能、应用场景与技术特性详解 一、核心功能 1. 自然语言转SQL查询 Vanna 允许用户通过自然语言提问&#xff08;如“显示2024年销售额最高的产品”&#xff09;&#xff0c;自动生成符合数据库规范的SQL查询语句。其底层采用 RAG&#xff08;检索增强生成&#xf…