MST

星途 面试题库

面试题:Go语言切片在并发场景下的优化与陷阱

在并发编程中使用Go语言切片,可能会遇到哪些陷阱?如何设计一个并发安全且高效的切片操作方案,比如实现一个支持并发读写的切片,需要考虑性能优化、资源竞争等问题,给出详细的设计思路和代码示例。
30.5万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

1. Go 语言切片在并发编程中的陷阱

  • 资源竞争:多个 goroutine 同时读写切片时,可能会发生数据竞争,导致结果不可预测。例如,一个 goroutine 正在写入切片,另一个 goroutine 同时读取切片,可能读到未完全写入的数据。
  • 动态扩容问题:切片在容量不足时会自动扩容,并发环境下扩容操作可能导致数据不一致。比如多个 goroutine 同时触发扩容,会造成复杂的竞争情况。

2. 设计思路

  • 互斥锁:使用 sync.Mutex 来保护对切片的读写操作。在每次读写切片前获取锁,操作完成后释放锁,以此避免资源竞争。
  • 读写锁:如果读操作远多于写操作,可以考虑使用 sync.RWMutex。读操作时使用读锁,允许多个 goroutine 同时读;写操作时使用写锁,保证只有一个 goroutine 能写,防止写时读和写时写的竞争。
  • 性能优化:尽量减少锁的持有时间,将复杂操作分解,在持有锁期间只进行必要的切片操作。可以考虑使用缓存,减少对实际切片的直接操作频率。

3. 代码示例(使用互斥锁)

package main

import (
    "fmt"
    "sync"
)

type SafeSlice struct {
    data []int
    mutex sync.Mutex
}

func (s *SafeSlice) Append(num int) {
    s.mutex.Lock()
    s.data = append(s.data, num)
    s.mutex.Unlock()
}

func (s *SafeSlice) Get(index int) (int, bool) {
    s.mutex.Lock()
    defer s.mutex.Unlock()
    if index < 0 || index >= len(s.data) {
        return 0, false
    }
    return s.data[index], true
}

func main() {
    safeSlice := SafeSlice{}
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(num int) {
            defer wg.Done()
            safeSlice.Append(num)
        }(i)
    }

    go func() {
        wg.Wait()
        for i := 0; i < 10; i++ {
            value, ok := safeSlice.Get(i)
            if ok {
                fmt.Println("Index", i, "Value", value)
            }
        }
    }()

    select {}
}

4. 代码示例(使用读写锁)

package main

import (
    "fmt"
    "sync"
)

type SafeSliceWithRWLock struct {
    data []int
    rwMutex sync.RWMutex
}

func (s *SafeSliceWithRWLock) Append(num int) {
    s.rwMutex.Lock()
    s.data = append(s.data, num)
    s.rwMutex.Unlock()
}

func (s *SafeSliceWithRWLock) Get(index int) (int, bool) {
    s.rwMutex.RLock()
    defer s.rwMutex.RUnlock()
    if index < 0 || index >= len(s.data) {
        return 0, false
    }
    return s.data[index], true
}

func main() {
    safeSlice := SafeSliceWithRWLock{}
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(num int) {
            defer wg.Done()
            safeSlice.Append(num)
        }(i)
    }

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(index int) {
            defer wg.Done()
            value, ok := safeSlice.Get(index)
            if ok {
                fmt.Println("Index", index, "Value", value)
            }
        }(i)
    }

    wg.Wait()
}