GO语言基础-数据竞争
2022-11-07 00:36:02    108    0    0
weibo-007

Map数据竞争

直接看一段例子

package main

import (
    "sync"
    "fmt"
)

func main() {
    m := map[int]int{}
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            if _, exists := m[i]; !exists {
                m[i] = i
            }
            wg.Done()
        }(i)
    }
    wg.Wait()
    fmt.Println(m)
}

这个例子可能有三种输出情况

可能输出情况

况1

输出结果:正常

map[0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9]

情况2

报fatal,错误信息显示存在读和写冲突

fatal error: concurrent map read and map write

情况3

报fatal,错误信息显示写冲突

fatal error: concurrent map writes

原因

map在go的内部实现其实是哈希表,哈希表结构体定义。重点关注flags字段。这个字段在处理冲突中起了关键作用。

type hmap struct {
	// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
	// Make sure this stays in sync with the compiler's definition.
	count     int // # live cells == size of map.  Must be first (used by len() builtin)
	flags     uint8
	B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
	noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
	hash0     uint32 // hash seed

	buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
	oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
	nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

	extra *mapextra // optional fields
}

1)写操作的时候会判断,并且写一下flags值

2)读操作也有这样的判断,和写操作的判断是一样的

快速发现

数据竞争出现的时候,并不是100%出现异常,也可能运行之后出现正常的情况,从而会误判程序没有问题

使用-race参数可以快速检测数据冲突的风险

xxxxxxxx@B000000400110Z ~ % go run -race /tmp/test4.go
==================
WARNING: DATA RACE
Read at 0x00c000124180 by goroutine 8:
  runtime.evacuate_fast32()

解决方案

解决方案1

使用锁实现map原子操作

package main

import (
	"fmt"
	"sync"
)

type myMap struct {
	sync.RWMutex
	m map[int]int
}

func main() {
	mp := myMap{}
	mp.m = map[int]int{}
	var wg sync.WaitGroup

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(i int) {
			mp.Lock()
			defer mp.Unlock()
			if _, exists := mp.m[i]; !exists {
				mp.m[i] = i
			}
			wg.Done()
		}(i)
	}
	wg.Wait()
	fmt.Println(mp.m)
}

 解决方案1

在go1.9之后,使用sync.Map{}实现map的原子操作

package main

import (
	"fmt"
	"sync"
)

func main() {
	m := sync.Map{}
	var wg sync.WaitGroup

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(i int) {
			if _, exists := m.Load(i); !exists {
				m.Store(i, i)
			}
			wg.Done()
		}(i)
	}
	wg.Wait()
	m.Range(func(key, value interface{}) bool {
		fmt.Println(key, value)
		return true
	})
}

Slice数据竞争

通常对 map 的并发读写问题很容易发现,因为一旦并发的更新一个 map 时,golang 会 panic,你可以从日志中很快发现这个问题。但是对 slice 进行 append 时也有并发安全问题,但是由于不会 panic 因此很容易被忽略,一旦没有对比校验实际数据量,很难发现问题。

这种情况目前只能用下面两种方式发现

可能输出情况

1)-race参数运行,但是这会造成5-10倍的性能损失,没有人会在线上开启,而且开启了也一定会执行到那块逻辑

2)校验运行结果和预期的结果,这个一般也很难做到,除非有意识去做测试

先来看一段程序可能的输出:

package main

import (
	"fmt"
	"sync"
)

var globle []int

func main() {
	globle = make([]int, 10)

	wg := sync.WaitGroup{}
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func() {
			globle = append(globle, 1)
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println(globle)
}

这段程序可能输出

xxxxx@192 ~ % for ((i=0; i<10; i++))
do
go run /tmp/test.go
done
[0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1]
[0 0 0 0 0 0 0 0 0 0 1]
[0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1]
[0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1]
[0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1]
[0 0 0 0 0 0 0 0 0 0 1 1 1 1]

快速发现 

可以看到,输出结果是不确定的,我么用-race检测数据竞争,注意,slice发生数据竞争的时候是没有fatal或者panic

xxxx@192 ~ % go run -race /tmp/test.go
==================
WARNING: DATA RACE
Read at 0x000104cfec50 by goroutine 8:
  main.main.func1()
      /tmp/test.go:17 +0x34

Previous write at 0x000104cfec50 by goroutine 7:
  main.main.func1()
      /tmp/test.go:17 +0xa8

Goroutine 8 (running) created at:
  main.main()
      /tmp/test.go:16 +0xcc

Goroutine 7 (finished) created at:
  main.main()
      /tmp/test.go:16 +0xcc
==================

解决方案

使用map锁机制实现并发安全

package main

import (
	"fmt"
	"sync"
)

var globle []int

func main() {
	globle = make([]int, 10)

	var mu sync.Mutex
	wg := sync.WaitGroup{}
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func() {
			mu.Lock()
			globle = append(globle, 1)
			mu.Unlock()
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println(globle)
}

 

 

 

 

Pre: DDD系列-领域划分

Next: DDD系列-领域建模

108
Sign in to leave a comment.
No Leanote account? Sign up now.
0 comments
Table of content