1.问题背景
线上出现数据一下查到一下查不到的情况,后面定位问题,发现子线程数据赋值给上级线程的时候,数据丢失了。
2.问题复现
func main() {
for i := 0; i < 100000; i++ {
ints := make([]int, 0)
var wg sync.WaitGroup
wg.Add(1)
go func(wg *sync.WaitGroup) {
ints = Get1(wg)
}(&wg)
wg.Wait()
if len(ints) == 0 {
fmt.Println("ces ")
}
}
}
func Get1(wg *sync.WaitGroup) []int {
defer func() {
wg.Done()
}()
ints := make([]int, 0)
for i := 0; i < 10000; i++ {
ints = append(ints, i)
}
return ints
}
上面可以复现出取值为0 的情况
3.问题疑惑
以上复现了问题,但是我发现1w次并发的时候特别少出现数据丢失的情况,10w次就频发了,这边我认为可以这样解释,10w个协程导致gc频发,数据丢失好像很合理。但是和实际情况不一致,实际,我们没开这么多协程,应该是没有频繁gc的情况。所以我们这复现了只是其中的一种情况。
4.问题确认
后面我调整代码
func main() {
for i := 0; i < 100000; i++ {
ints := make([]int, 0)
var wg sync.WaitGroup
wg.Add(1)
var mu sync.Mutex
go func(wg *sync.WaitGroup) {
mu.Lock()
ints = Get1(wg)
mu.Unlock()
}(&wg)
wg.Wait()
mu.Lock()
if len(ints) == 0 {
fmt.Println("ces ")
}
mu.Unlock()
}
}
func Get1(wg *sync.WaitGroup) []int {
defer func() {
wg.Done()
}()
ints := make([]int, 0)
for i := 0; i < 10000; i++ {
ints = append(ints, i)
}
return ints
}
在主子协程上面赋值取值操作进行一个加锁操作,发现问题消失了,那说明我们之前的代码是线程不安全的,但是我们理论上理解,使用waitgroup应该会等到子协程执行完毕的,理论上会执行正确的。但是多线程环境下,读取和赋值的顺序是不确定的,会产生竞态条件,因此在我们加上锁之后,执行就不会再出现数据读取异常的情况,这样也是符合我们生产的问题,归根结底还是需要对同一个共享数据操作需要加上锁,或者使用chan的方式去获取参数。