[提问]golang 的interface真难受啊,我要怎么解决这个空指针问题,大佬求救,在线等~

已经判断了是否为nil,结果还是报错空指针
panic: runtime error: invalid memory address or nil pointer dereference

我知道golang 的interface==nil的行为是内部类型和值都为空才是nil,但是我要怎么声明一个interface,type为int,值为nil,问了gpt啥的都回复的不对,太难了

这代码运行在高并发环境,疑似内存同步问题,实际上我使用debug查看 next.arr[idx]其实是非空的

代码如图


这时候v的值
Clip_2024-05-09_16-08-39

1 个赞

源码和复现的测试代码如下

源码
import (
    "runtime"
    "sync/atomic"
    "unsafe"
)

//链表+数组实现队列,无锁

type layer struct {
    id   uint64
    arr  []any
    _    [cpuCacheKillerPaddingLength]byte
    next unsafe.Pointer
    take uint32
}

type lkArrQueue[T any] struct {
    zero      T
    layerMask uint64 //层掩码

    produceId uint64
    _         [cpuCacheKillerPaddingLength]byte
    consumeId uint64
    _         [cpuCacheKillerPaddingLength]byte

    //head.next为当前层
    head unsafe.Pointer
    _    [cpuCacheKillerPaddingLength]byte
    tail unsafe.Pointer
}

func (q *lkArrQueue[T]) Enqueue(v T) {
    //获取pid
    pid := atomic.AddUint64(&q.produceId, 1)
    //获取层中的索引
    lIdx := pid & q.layerMask
    //根据pid获取layerId
    layerId := pid / (q.layerMask + 1)
    //新建layer
    if lIdx == 0 {
        l := newLayer(layerId, int(q.layerMask+1))
        l.arr[0] = v
        lp := unsafe.Pointer(l)
        for i := 0; ; i++ {
            tail := (*layer)(atomic.LoadPointer(&q.tail))
            if tail.id != layerId-1 {
                if i > 10 {
                    runtime.Gosched()
                }
                continue
            }
            atomic.StorePointer(&tail.next, lp)
            if atomic.CompareAndSwapPointer(&q.tail, unsafe.Pointer(tail), lp) {
                break
            }
        }
        return
    } else {
        //获取tail
        for i := 0; ; i++ {
            tail := (*layer)(atomic.LoadPointer(&q.tail))
            if tail.id == layerId {
                tail.arr[lIdx] = v
                return
            }
            //tail已经被替换,从head开始找到当前的layer
            if tail.id > layerId {
                head := (*layer)(atomic.LoadPointer(&q.head))
                for {
                    if head.id == layerId {
                        head.arr[lIdx] = v
                        return
                    }
                    head = (*layer)(head.next)
                }
            } else {
                if i > 10 {
                    runtime.Gosched()
                }
            }
        }
    }
}

func (q *lkArrQueue[T]) Dequeue() (T, bool) {
    for {
        pid := atomic.LoadUint64(&q.produceId)
        cid := atomic.LoadUint64(&q.consumeId) + 1
        if pid <= cid {
            return q.zero, false
        }
        hp := atomic.LoadPointer(&q.head)
        next := (*layer)((*layer)(hp).next)
        if next == nil {
            return q.zero, false
        }
        layerId := cid / (q.layerMask + 1)
        if next.id == layerId {
            if atomic.CompareAndSwapUint64(&q.consumeId, cid-1, cid) {
                for i := 0; ; i++ {
                    idx := cid & q.layerMask
                    v := next.arr[idx]
                    if v == nil {
                        if i > 10 {
                            runtime.Gosched()
                        }
                        continue
                    }
                    atomic.AddUint32(&next.take, 1)
                    return v.(T), true
                }
            }
            continue
        }
        if next.id == layerId-1 {
            if atomic.CompareAndSwapUint64(&q.consumeId, cid-1, cid) {
                for i := 0; ; i++ {
                    var n *layer
                    for j := 0; ; j++ {
                        n = (*layer)(atomic.LoadPointer(&next.next))
                        if n != nil {
                            break
                        } else if j > 10 {
                            runtime.Gosched()
                        }
                    }
                    idx := cid & q.layerMask
                    v := n.arr[idx]
                    if v == nil {
                        if i > 10 {
                            runtime.Gosched()
                        }
                        continue
                    }
                    atomic.AddUint32(&n.take, 1)
                    //读取新的层了
                    if idx == 0 {
                        for {
                            if (*layer)(hp).take == uint32(q.layerMask+1) {
                                if atomic.CompareAndSwapPointer(&q.head, hp, unsafe.Pointer(next)) {
                                    break
                                }
                            } else {
                                runtime.Gosched()
                            }
                        }
                    }
                    return v.(T), true
                }
            }
        }
        runtime.Gosched()
    }
}

func (q *lkArrQueue[T]) Size() int {
    pid := atomic.LoadUint64(&q.produceId)
    cid := atomic.LoadUint64(&q.consumeId)
    if pid >= cid {
        return int(pid - cid)
    } else {
        //id溢出,兼容一下
        return int(pid + (1 << 63) - cid)
    }
}

func newLayer(id uint64, size int) *layer {
    return &layer{
        id:  id,
        arr: make([]any, size),
    }
}

func newLinkArrQueue[T any](layerSize int) Queue[T] {
    if layerSize <= 3 {
        layerSize = 4
    }
    layerSize |= layerSize >> 1
    layerSize |= layerSize >> 2
    layerSize |= layerSize >> 4
    layerSize |= layerSize >> 8
    layerSize |= layerSize >> 16
    layerSize += 1
    l := newLayer(0, 0)
    id := uint64(layerSize) - 1
    l.take = uint32(layerSize)
    return &lkArrQueue[T]{
        consumeId: id,
        produceId: id,
        layerMask: uint64(layerSize - 1),
        head:      unsafe.Pointer(l),
        tail:      unsafe.Pointer(l),
    }
}
测试代码

func BenchmarkQ(b *testing.B) {
    goNum := 32
    queue := NewQueue(WithTypeArrayLink[int](128))
    over := int32(0)
    b.Cleanup(func() {
        atomic.StoreInt32(&over, 1)
    })
    for i := 0; i < goNum*2; i++ {
        go func() {
            for {
                if queue.Size() > 10000 {
                    runtime.Gosched()
                } else {
                    for j := 0; j < 1000; j++ {
                        queue.Enqueue(1)
                    }
                }
                if atomic.LoadInt32(&over) == 1 {
                    return
                }
            }
        }()
    }
    b.ResetTimer()
    b.SetParallelism(goNum)
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            for {
                _, ok := queue.Dequeue()
                if ok {
                    break
                }
            }
        }
    })
}

可能需要多次运行测试,不是每次都能成功复现的

顺便补充,这种情况网常见说法
a==nil||(reflect.ValueOf(a).Kind() == reflect.Ptr && reflect.ValueOf(a).IsNil())
也是不好用的
go的interface太难了

最后咋解决的

无法解决,加了个是否set的字段,恶心坏了

当数组有保存过int时,就不可能为nil了,要么取出时自己重置为nil

type layer struct {
id uint64
arr []any
_ [cpuCacheKillerPaddingLength]byte
next unsafe.Pointer
take uint32
}

你确定吗,我怎么感觉我学的不是跟你同一个东西

看了下代码,你这情况应该不会出现才对,arr的interface数组默认都是nil,设置过就是int值,不存在type=int, data=nil的情况,泛型优化了?

我估计是内存同步问题,勉强能算go的bug,没办法