02 December 2020

记录一个初学者bug。

在并发场景下,使用slice时,需要时刻谨记,slice是共用底层数组的

intSlice = intSlice[0:0]

这里实际上并没有清空slice,只是重置了位置指针,从而造成数据覆盖,后面继续使用intSlice时会覆盖之前的数据。

错误示例代码:

package main

import (
    "fmt"
    "sync"
    "time"
    "unsafe"
)

// 并发度参数
var consumerCount = 2
var producerLogicDuration = 100000 * time.Nanosecond			// 减小该参数可增加获取到重复数字的概率
var consumerLogicDuration = 100000 * time.Nanosecond

// 业务参数
var wgStatic sync.WaitGroup
var dupMap sync.Map
var intSlice []int
var sliceChan chan []int

func main() {
    sliceChan = make(chan []int, 10)
    for i := 0; i < consumerCount; i++ {
        wgStatic.Add(1)
        go consume(sliceChan, i)
    }

    wgStatic.Add(1)
    go produce()

    wgStatic.Wait()
}

func produce() {
    defer wgStatic.Done()
    i := 0
    index := 0
    for {
        intSlice = append(intSlice, i)
        if index > 1 {
            sliceChan <- intSlice
            intSlice = intSlice[0:0]	// wrong
            //intSlice = nil 			// correct
            index = 0
        } else {
            index++
        }
        if i > 1000 {
            break
        }
        i++

        // mock to do some logic
        time.Sleep(producerLogicDuration)
    }
    close(sliceChan)
}

func consume(sliceChan chan []int, goroutineID int) {
    defer wgStatic.Done()

    for s := range sliceChan {
        fmt.Printf("rid = %v, s = %v, &array = %v, &s = %p\n", goroutineID, s, unsafe.Pointer(&s[0]), &s)
        for _, num := range s {
            actual, loaded := dupMap.LoadOrStore(num, goroutineID)
            if loaded {
                errMsg := fmt.Sprintf("key is loaded by goroutine already, key = %v, actual_goroutineID = %v," +
                    " current_goroutineID = %v\n", num, actual, goroutineID)
                panic(errMsg)
            }
        }

        // mock to do some logic
        time.Sleep(consumerLogicDuration)
    }
}