记一次go中map并发引起的事故
错误使用map引发的血案
前言
最近业务中,同事使用map
来接收返回的结果,使用waitGroup
来并发的处理执行返回的结果,结果上线之后,直接崩了。
日志大量的数据库缓存池连接失败
{"ecode":-500,"message":"timed out while checking out a connection from connection pool"}
{"ecode":-500,"message":"connection(xxxxxxxxx:xxxxx) failed to write: context deadline exceeded"}
场景复原
先来看来伪代码
一个全局的map
,然后WaitGroup
开启一组协程并发的读写数据,写入内容到map
中。
package main
import (
"fmt"
"sync"
"time"
)
var count = 300
func main() {
var data = make(map[int]string, count)
var wg sync.WaitGroup
for i := 0; i < count; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
time.Sleep(time.Second * 1)
mockSqlPool()
data[i] = "test"
}(i)
}
fmt.Println("-----------WaitGroup执行结束了-----------")
wg.Wait()
}
// 模拟数据库的连接和释放
func mockSqlPool() {
defer fmt.Println("关闭pool")
fmt.Println("我是pool")
}
运行的输出
...
我是pool
关闭pool
我是pool
fatal error: 关闭pool
concurrent map writes
我是pool
goroutine 56 [running]:
runtime.throw(0x10d3923, 0x15)
/usr/local/go/src/runtime/panic.go:774 +0x72 fp=0xc00023cf20 sp=0xc00023cef0 pc=0x10298d2
runtime.mapassign_fast64(0x10b29e0, 0xc000066180, 0x16, 0x0)
/usr/local/go/src/runtime/map_fast64.go:101 +0x350 fp=0xc00023cf60 sp=0xc00023cf20 pc=0x100f620
main.main.func1(0xc00008c004, 0xc000066180, 0x16)
/Users/yj/Go/src/Go-POINT/map/main.go:23 +0x87 fp=0xc00023cfc8 sp=0xc00023cf60 pc=0x109a297
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:1357 +0x1 fp=0xc00023cfd0 sp=0xc00023cfc8 pc=0x1053a51
created by main.main
/Users/yj/Go/src/Go-POINT/map/main.go:18 +0xbb
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc00008c004)
/usr/local/go/src/runtime/sema.go:56 +0x42
sync.(*WaitGroup).Wait(0xc00008c004)
/usr/local/go/src/sync/waitgroup.go:130 +0x64
main.main()
/Users/yj/Go/src/Go-POINT/map/main.go:27 +0x138
goroutine 22 [semacquire]:
internal/poll.runtime_Semacquire(0xc00008606c)
/usr/local/go/src/runtime/sema.go:61 +0x42
internal/poll.(*fdMutex).rwlock(0xc000086060, 0xc000030500, 0x1097137)
/usr/local/go/src/internal/poll/fd_mutex.go:154 +0xad
internal/poll.(*FD).writeLock(...)
/usr/local/go/src/internal/poll/fd_mutex.go:239
internal/poll.(*FD).Write(0xc000086060, 0xc000226030, 0xb, 0x10, 0x0, 0x0, 0x0)
/usr/local/go/src/internal/poll/fd_unix.go:255 +0x5e
os.(*File).write(...)
/usr/local/go/src/os/file_unix.go:276
os.(*File).Write(0xc000084008, 0xc000226030, 0xb, 0x10, 0xc0000306b0, 0x103d37e, 0xc00000c060)
/usr/local/go/src/os/file.go:153 +0x77
fmt.Fprintln(0x10ec4e0, 0xc000084008, 0xc000030730, 0x1, 0x1, 0x10459b6, 0xc00000c060, 0x3)
/usr/local/go/src/fmt/print.go:265 +0x8b
fmt.Println(...)
/usr/local/go/src/fmt/print.go:274
main.mockSqlPool()
/Users/yj/Go/src/Go-POINT/map/main.go:35 +0x104
main.main.func1(0xc00008c004, 0xc000066180, 0x4)
/Users/yj/Go/src/Go-POINT/map/main.go:21 +0x63
created by main.main
/Users/yj/Go/src/Go-POINT/map/main.go:18 +0xbb
goroutine 192 [semacquire]:
internal/poll.runtime_Semacquire(0xc00009e06c)
/usr/local/go/src/runtime/sema.go:61 +0x42
internal/poll.(*fdMutex).rwlock(0xc00009e060, 0x10fae00, 0xc00023ad00)
/usr/local/go/src/internal/poll/fd_mutex.go:154 +0xe9
internal/poll.(*FD).writeLock(...)
/usr/local/go/src/internal/poll/fd_mutex.go:239
internal/poll.(*FD).Write(0xc00009e060, 0xc000246100, 0xb, 0x10, 0x0, 0x0, 0x0)
/usr/local/go/src/internal/poll/fd_unix.go:255 +0x6f
os.(*File).write(...)
/usr/local/go/src/os/file_unix.go:276
os.(*File).Write(0xc00009c008, 0xc000246100, 0xb, 0x10, 0xc000124580, 0x40, 0x0)
/usr/local/go/src/os/file.go:153 +0xa7
fmt.Fprintln(0x1158520, 0xc00009c008, 0xc00014d728, 0x1, 0x1, 0x107e3e6, 0xc0000d8100, 0x16)
/usr/local/go/src/fmt/print.go:265 +0xb3
fmt.Println(...)
/usr/local/go/src/fmt/print.go:274
main.mockSqlPool()
/Users/yj/Go/src/Go-POINT/map/main.go:35 +0x129
main.main.func1(0xc0000a0004, 0xc000088180, 0x8f)
/Users/yj/Go/src/Go-POINT/map/main.go:21 +0x75
created by main.main
/Users/yj/Go/src/Go-POINT/map/main.go:18 +0x102
goroutine 193 [semacquire]:
internal/poll.runtime_Semacquire(0xc00009e06c)
/usr/local/go/src/runtime/sema.go:61 +0x42
internal/poll.(*fdMutex).rwlock(0xc00009e060, 0x10fae00, 0xc000286410)
/usr/local/go/src/internal/poll/fd_mutex.go:154 +0xe9
internal/poll.(*FD).writeLock(...)
/usr/local/go/src/internal/poll/fd_mutex.go:239
internal/poll.(*FD).Write(0xc00009e060, 0xc0000a01a0, 0xb, 0x10, 0x0, 0x0, 0x0)
/usr/local/go/src/internal/poll/fd_unix.go:255 +0x6f
os.(*File).write(...)
/usr/local/go/src/os/file_unix.go:276
os.(*File).Write(0xc00009c008, 0xc0000a01a0, 0xb, 0x10, 0xc0001245c0, 0x40, 0x0)
/usr/local/go/src/os/file.go:153 +0xa7
fmt.Fprintln(0x1158520, 0xc00009c008, 0xc00014df28, 0x1, 0x1, 0x107e3e6, 0xc0000d8100, 0x17)
/usr/local/go/src/fmt/print.go:265 +0xb3
fmt.Println(...)
/usr/local/go/src/fmt/print.go:274
main.mockSqlPool()
/Users/yj/Go/src/Go-POINT/map/main.go:35 +0x129
main.main.func1(0xc0000a0004, 0xc000088180, 0x90)
/Users/yj/Go/src/Go-POINT/map/main.go:21 +0x75
created by main.main
/Users/yj/Go/src/Go-POINT/map/main.go:18 +0x102
goroutine 194 [semacquire]:
internal/poll.runtime_Semacquire(0xc00009e06c)
/usr/local/go/src/runtime/sema.go:61 +0x42
internal/poll.(*fdMutex).rwlock(0xc00009e060, 0x10fae00, 0xc00023add0)
/usr/local/go/src/internal/poll/fd_mutex.go:154 +0xe9
internal/poll.(*FD).writeLock(...)
/usr/local/go/src/internal/poll/fd_mutex.go:239
internal/poll.(*FD).Write(0xc00009e060, 0xc000246110, 0xb, 0x10, 0x0, 0x0, 0x0)
/usr/local/go/src/internal/poll/fd_unix.go:255 +0x6f
os.(*File).write(...)
/usr/local/go/src/os/file_unix.go:276
os.(*File).Write(0xc00009c008, 0xc000246110, 0xb, 0x10, 0xc000124600, 0x40, 0x0)
/usr/local/go/src/os/file.go:153 +0xa7
fmt.Fprintln(0x1158520, 0xc00009c008, 0xc000146728, 0x1, 0x1, 0x107e3e6, 0xc0000d8100, 0x18)
/usr/local/go/src/fmt/print.go:265 +0xb3
fmt.Println(...)
/usr/local/go/src/fmt/print.go:274
main.mockSqlPool()
/Users/yj/Go/src/Go-POINT/map/main.go:35 +0x129
main.main.func1(0xc0000a0004, 0xc000088180, 0x91)
/Users/yj/Go/src/Go-POINT/map/main.go:21 +0x75
created by main.main
/Users/yj/Go/src/Go-POINT/map/main.go:18 +0x102
会发现很多goroutine
处于semacquire
状态,说明这些goroutine
正在等待被信号量唤醒。但是这时候waitGroup
已经因为panic
退出了,这些goroutine
不会在通过waitGroup.Done()
退出,造成这些goroutine
一直阻塞到这,最后的结果就是这些goroutine
占用的数据库连接不能被释放。
关于waitGroup
的信号量
整个Wait()
会被runtime_Semacquire
阻塞,直到等到全部退出的信号量;
Done()
会在最后一次的时候通过runtime_Semrelease
发出取消阻塞的信号,然后被runtime_Semacquire
阻塞的Wait()
就可以退出了;
上面涉及到的几种状态
- semacquire 状态,这个状态表示等待调用
- Waiting 等待状态。线程在等待某件事的发生。例如等待网络数据、硬盘;调用操作系统 API;等待内存同步访问条件 ready,如 atomic, mutexes
- Runnable 就绪状态。只要给 CPU 资源我就能运行
原因
上面的错误原因有两个:
1、
map
不是并发安全,并发写的时候会触发panic
;2、避免在循环中连接数据库;
参考
【map 并发崩溃一例】https://xargin.com/map-concurrent-throw/
记一次go中map并发引起的事故的更多相关文章
- golang中map并发读写问题及解决方法
一.map并发读写问题 如果map由多协程同时读和写就会出现 fatal error:concurrent map read and map write的错误 如下代码很容易就出现map并发读写问题 ...
- 记一次项目中解决 -- 并发减库存超卖问题过程(Java)
起因:项目中要做预约功能,首先每天的余票都是有上限的,自然不能出现超卖的情况 基于我们项目是单体分布式的springcloud部署,我想了下 第一种方法,直接mysql加行锁,要update这条库存数 ...
- Hadoop MapReduce概念学习系列之map并发任务数和reduce并发任务数的原理和代码实现(十八)
首先,来说的是,reduce并发任务数,默认是1. 即,在jps后,出现一个yarnchild.之后又消失. 这里,我控制reduce并发任务数6 有多少个reduce的并发任务数可以控制,但有多少个 ...
- Java 中的并发工具类
Java 中的并发工具类 CountDownLatch public class JoinCountDownLatchTest { public static void main(String[] a ...
- Go语言中的并发编程
并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因. Go语言中的并发编程 并发与并行 并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天) ...
- C#中实现并发的几种方法的性能测试
C#中实现并发的几种方法的性能测试 0x00 起因 去年写的一个程序因为需要在局域网发送消息支持一些命令和简单数据的传输,所以写了一个C/S的通信模块.当时的做法很简单,服务端等待链接,有用户接入后开 ...
- WCF初探-28:WCF中的并发
理解WCF中的并发机制 在对WCF并发机制进行理解时,必须对WCF初探-27:WCF中的实例化进行理解,因为WCF中的并发特点是伴随着服务实例上下文实现的.WCF的实例上下文模型可以通过Instanc ...
- java8中map的meger方法的使用
java8中map有一个merge方法使用示例: /** * 打印出包含号码集的label的集合 * * @param args */ public static void main(String[] ...
- 【转】Python 中map、reduce、filter函数
转自:http://www.blogjava.net/vagasnail/articles/301140.html?opt=admin 介绍下Python 中 map,reduce,和filter 内 ...
随机推荐
- ElasticSearch DSL 查询
公号:码农充电站pro 主页:https://codeshellme.github.io DSL(Domain Specific Language)查询也叫做 Request Body 查询,它比 U ...
- 数据处理_HIVE增量ETL的一种方式
适用场景: 贴源层主表历史数据过大,ETL不涉及历史数据对比或聚合 处理流程: 1.确定一个业务主键字段或物理主键字段 2.确定一个可以判断增量数据范围的字段,这取决于具体的业务场景,一般选用记录的创 ...
- 使用 Tye 辅助开发 k8s 应用竟如此简单(五)
续上篇,这篇我们来进一步探索 Tye 更多的使用方法.本篇我们来了解一下如何在 Tye 中实现对分布式链路追踪. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如果您是首次 ...
- Debian 基本使用进阶
系统安装好了我们,迫不及待的想要在Linux系统中肆意翱翔.如果是刚刚接触Linux的系统的话,可能一时间还无法适应Linux的系统环境.对于使用Debian来做服务器的选择,最好的练习方式的就是使用 ...
- Vuex入门、同步异步存取值进阶版
关注公众号查看原文: 1. vueX介&绍安装 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方 ...
- 后端程序员之路 17、LaTeX公式
之前的文章写了两个公式:d(x,y)=\sqrt{\sum_{i=1}^{n}(x_i-y_i)^2} H_x=-\sum_{i=1}^{n}p(x_i)\log_{2}{p(x_i)} LaTex ...
- Python3.x 基础练习题100例(61-70)
练习61: 题目: 打印出杨辉三角形. 程序: if __name__ == '__main__': a = [] for i in range(10): a.append([]) for j in ...
- matplotlib工具栏源码探析三(添加、删除自定义工具项)
转: matplotlib工具栏源码探析三(添加.删除自定义工具项) matplotlib工具栏源码探析二(添加.删除内置工具项)探讨了工具栏内置工具项的管理,除了内置工具项,很多场景中需要自定义工具 ...
- phpMyAdmin Transformation 任意文件包含/远程代码执行漏洞
漏洞参考 https://yq.aliyun.com/articles/679633 国外提供了一个在线测试的靶场 默认密码 root toor https://www.vsplate.c ...
- Prometheus自定义指标
1. 自定义指标 为了注册自定义指标,请将MeterRegistry注入到组件中,例如: public class Dictionary { private final List<String ...