前言

sync.Mapmap+sync.Mutexmap+sync.RWMutex

注:使用的Go版本是1.18

一个并发写map的例子

package main

import (
   "fmt"
)

func main() {
   m := map[int]bool{}
   go func() {
      defer func() {
         a := recover()
         fmt.Println("1", a)
      }()
      for {
         m[10] = true
      }
   }()
   go func() {
      defer func() {
         a := recover()
         fmt.Println("2", a)
      }()
      for {
         m[10] = true
      }
   }()
   select {}
}
复制代码

这个例子是两个goroutine并发写一个map的一个key,在对于的函数里,使用了defer+recover()试图捕获并发写map产生的异常,主goroutine通过select{}进行阻塞等待。

我们运行这段代码,会产生以下错误信息:

fatal error: concurrent map writes

goroutine 7 [running]:
runtime.throw({0x2c7b35?, 0x2bc2c0?}) <-重点
        C:/Program Files/Go/src/runtime/panic.go:992 +0x76 fp=0xc000051f68 sp=0xc000051f38 pc=0x253076
runtime.mapassign_fast64(0x2b65c0, 0xc000100480, 0xa) <-重点

        C:/Program Files/Go/src/runtime/map_fast64.go:177 +0x2b4 fp=0xc000051fa0 sp=0xc000051f68 pc=0x22fbd4
main.main.func2()

        D:/Github/him/_test/test.go:24 +0x50 fp=0xc000051fe0 sp=0xc000051fa0 pc=0x2ab770
runtime.goexit()

        C:/Program Files/Go/src/runtime/asm_amd64.s:1571 +0x1 fp=0xc000051fe8 sp=0xc000051fe0 pc=0x27be81
created by main.main

        D:/Github/him/_test/test.go:18 +0x7d

goroutine 1 [select (no cases)]:
main.main()
        D:/Github/him/_test/test.go:27 +0x85

goroutine 6 [runnable]:
main.maiD:/Github/him/_test/test.go:15 +0x50
created by main.main
        D:/Github/him/_test/test.go:9 +0x50

Process finished with the exit code 2
复制代码
mapassign_fast64()runtime.throw()

我们先看看runtime/map_fast64.go的mapassign_fast64()函数:

func mapassign_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
   // 省略代码。。。
    
   // 第一处
   if h.flags&hashWriting != 0 { 
      throw("concurrent map writes")
   }
    
   // 第二处
   h.flags ^= hashWriting

   // 省略代码。。。
again:
   // 省略代码。。。

bucketloop:
   // 省略代码。。。

done:
   // 省略代码。。。
   // 第三处
   if h.flags&hashWriting == 0 {
      throw("concurrent map writes")
   }
   // 第四处
   h.flags &^= hashWriting
   return elem
}
复制代码

其中:

hashWriting  = 4 // 表示有 goroutine 正在写map
复制代码
hashWritinghashWriting

我们按照上面分的4个位置:

第一处

if h.flags&hashWriting != 0 { 
   throw("concurrent map writes")
}
复制代码

在函数的入口处,先检查是否有goroutine正在写map,如果有说明并发写map,直接throw()

第二处

h.flags ^= hashWriting
复制代码
异或操作或操作第一处h.flagshashWriting第三处

第三处

if h.flags&hashWriting == 0 {
   throw("concurrent map writes")
}
复制代码
h.flags&hashWriting第二步h.flags ^= hashWritingh.flagshashWriting

第四处

h.flags &^= hashWriting
复制代码
&^
第一个操作数第二个操作数结果解释
0000&^0
0100&^1
1011&^0
1101&^1
除了hashWriting对应的位h.flagshashWriting对应的位h.flags
h.flagshashWriting

简单来说

h.flagsthrow()
throw()

runtime.throw()

func throw(s string) {
   systemstack(func() {
      print("fatal error: ", s, "\n")
   })
   gp := getg()
   if gp.m.throwing == 0 {
      gp.m.throwing = 1
   }
   fatalthrow()
   *(*int)(nil) = 0 // not reached
}
复制代码
fatal error:  xxxfatalthrow()fatalthrow()

runtime.fatalthrow()

我们直接看这个函数的注释:

// fatalthrow implements an unrecoverable runtime throw. It freezes the
// system, prints stack traces starting from its caller, and terminates the
// process.
复制代码

简单来说就是:

fatalthrow()recover()
fatalthrow()exit(2)Process finished with the exit code 2
recover()recover()

总结

runtime.throw()recover()sync.Mutexsync.RWMutextsync.Map