MST
星途 面试题库

面试题:Go语言切片在并发场景下作为函数参数传递的复杂性

在一个并发的Go程序中,多个goroutine同时调用一个接受切片作为参数的函数,并且在函数内部对切片进行读写操作。请阐述可能出现的数据竞争问题以及如何通过合适的机制(如互斥锁、通道等)来避免这些问题,同时说明不同机制在这种场景下的优缺点。
21.4万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

数据竞争问题

当多个goroutine同时对同一个切片进行读写操作时,可能会出现数据竞争。因为在并发环境下,多个goroutine对共享资源(这里是切片)的访问顺序是不确定的。例如,一个goroutine可能正在读取切片中的某个元素,而另一个goroutine同时对该元素进行修改,这就会导致读取到的数据可能是不一致的或者是未定义的状态。

使用互斥锁避免数据竞争

  • 实现方式:在函数内部使用sync.Mutex。在对切片进行读写操作前,先调用mutex.Lock()锁定互斥锁,操作完成后调用mutex.Unlock()解锁。
package main

import (
    "fmt"
    "sync"
)

var mu sync.Mutex
var data []int

func modifySlice(newValue int) {
    mu.Lock()
    data = append(data, newValue)
    mu.Unlock()
}

func readSlice() {
    mu.Lock()
    fmt.Println(data)
    mu.Unlock()
}
  • 优点:实现简单直观,对于控制对共享资源的访问非常有效,可以确保同一时间只有一个goroutine能够访问切片,从而避免数据竞争。
  • 缺点:性能开销较大,因为互斥锁会阻止其他goroutine对共享资源的访问,可能会导致goroutine阻塞,降低程序的并发性能。而且如果使用不当,容易出现死锁。

使用通道避免数据竞争

  • 实现方式:可以创建一个通道,将对切片的操作请求通过通道发送给一个专门处理这些请求的goroutine。这个专门的goroutine从通道接收请求并执行相应的读写操作。
package main

import (
    "fmt"
)

type SliceOp struct {
    value int
    op    string
}

func sliceHandler(sliceChan chan SliceOp) {
    var data []int
    for op := range sliceChan {
        switch op.op {
        case "append":
            data = append(data, op.value)
        case "read":
            fmt.Println(data)
        }
    }
}
  • 优点:基于消息传递的模型,避免了共享状态,使得代码更易于理解和维护。可以充分利用Go语言的并发特性,在不使用锁的情况下实现并发安全。
  • 缺点:实现相对复杂,需要仔细设计通道的使用方式和数据结构。如果通道的缓冲设置不合理,可能会导致goroutine阻塞或数据丢失。同时,通道的使用可能会引入额外的通信开销。