MST

星途 面试题库

面试题:Go语言切片并发复制与传递的复杂场景处理

假设在一个高并发的Go程序中,多个协程同时对同一个切片进行复制和传递操作,可能会出现哪些数据竞争问题?请详细描述并给出相应的解决方案,包括但不限于使用`sync`包中的工具。
46.5万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

可能出现的数据竞争问题

  1. 读 - 写竞争:当一个协程正在复制切片(读取切片数据),同时另一个协程对该切片进行写入操作(比如追加新元素、修改元素值等),会导致读取到不一致的数据。例如,复制操作可能读取到部分修改前和部分修改后的数据。
  2. 写 - 写竞争:多个协程同时对切片进行写入操作,比如同时追加元素,会导致切片内部数据结构的不一致,可能出现元素丢失、重复或者切片扩容机制紊乱等问题。

解决方案

  1. 使用sync.Mutex
package main

import (
    "fmt"
    "sync"
)

func main() {
    var mu sync.Mutex
    var slice []int
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            mu.Lock()
            // 复制切片
            newSlice := make([]int, len(slice))
            copy(newSlice, slice)
            // 模拟写入操作
            slice = append(slice, id)
            mu.Unlock()
        }(i)
    }

    wg.Wait()
    fmt.Println(slice)
}
- 原理:通过`sync.Mutex`的`Lock`和`Unlock`方法,保证在同一时刻只有一个协程能够访问和修改切片,从而避免数据竞争。

2. 使用sync.RWMutex(适用于读多写少场景)

package main

import (
    "fmt"
    "sync"
)

func main() {
    var rwmu sync.RWMutex
    var slice []int
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        if i%2 == 0 {
            wg.Add(1)
            go func(id int) {
                defer wg.Done()
                rwmu.Lock()
                // 复制切片
                newSlice := make([]int, len(slice))
                copy(newSlice, slice)
                // 模拟写入操作
                slice = append(slice, id)
                rwmu.Unlock()
            }(i)
        } else {
            wg.Add(1)
            go func() {
                defer wg.Done()
                rwmu.RLock()
                // 复制切片
                newSlice := make([]int, len(slice))
                copy(newSlice, slice)
                rwmu.RUnlock()
            }()
        }
    }

    wg.Wait()
    fmt.Println(slice)
}
- 原理:`sync.RWMutex`允许多个协程同时进行读操作(使用`RLock`和`RUnlock`),但在写操作时(使用`Lock`和`Unlock`)会独占切片,防止其他读写操作,适用于读多写少的场景,提高并发性能。

3. 使用sync.Cond(更复杂场景下协调读写)

package main

import (
    "fmt"
    "sync"
)

func main() {
    var mu sync.Mutex
    cond := sync.NewCond(&mu)
    var slice []int
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        if i%2 == 0 {
            wg.Add(1)
            go func(id int) {
                defer wg.Done()
                mu.Lock()
                for len(slice) > 0 {
                    cond.Wait()
                }
                // 复制切片
                newSlice := make([]int, len(slice))
                copy(newSlice, slice)
                // 模拟写入操作
                slice = append(slice, id)
                cond.Broadcast()
                mu.Unlock()
            }(i)
        } else {
            wg.Add(1)
            go func() {
                defer wg.Done()
                mu.Lock()
                for len(slice) == 0 {
                    cond.Wait()
                }
                // 复制切片
                newSlice := make([]int, len(slice))
                copy(newSlice, slice)
                cond.Broadcast()
                mu.Unlock()
            }()
        }
    }

    wg.Wait()
    fmt.Println(slice)
}
- 原理:`sync.Cond`基于`sync.Mutex`,通过`Wait`方法使协程等待特定条件(如切片为空或不为空),`Broadcast`方法通知其他等待的协程,用于更复杂的读写协调场景,避免不必要的等待和竞争。