• 记一次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/

  • 相关阅读:
    EF-入门操作
    Razor 页面解说
    Razor_02 第一个应用程序+Model+EF 添加
    Razor_01 第一个应用程序
    LazyCoder修仙之路
    .NET高级特性-Emit(2.2)属性
    .NET高级特性-Emit(2.1)字段
    .NET高级特性-Emit(2)类的定义
    .NET高级特性-Emit(1)
    asp.net core高级应用:TagHelper+Form
  • 原文地址:https://www.cnblogs.com/ricklz/p/14693855.html
Copyright © 2020-2023  润新知