MST

星途 面试题库

面试题:Go语言数组与切片在并发场景下的特性及应用

在并发编程场景中,Go语言的数组和切片各有哪些特性?如何根据这些特性来选择合适的数据结构以避免数据竞争和提高程序的并发性能?请结合具体的并发模型(如生产者 - 消费者模型)进行说明。
31.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

Go语言数组特性

  1. 固定长度:声明时需要指定长度,且长度不可变。例如 var arr [5]int,长度为5,后续不能改变数组长度。
  2. 值类型:数组作为参数传递时,是值传递,会完整复制一份数组。例如:
package main

import (
    "fmt"
)

func modifyArray(arr [3]int) {
    arr[0] = 100
}

func main() {
    a := [3]int{1, 2, 3}
    modifyArray(a)
    fmt.Println(a) // 输出 [1 2 3],原数组未改变
}

Go语言切片特性

  1. 动态长度:切片是基于数组的动态数据结构,长度可变。通过 make 函数创建,如 s := make([]int, 0, 10),初始长度为0,容量为10,随着元素添加,长度和容量可动态变化。
  2. 引用类型:切片作为参数传递时,传递的是切片头信息(包含指向底层数组的指针、长度和容量),对切片的修改会影响到原切片。例如:
package main

import (
    "fmt"
)

func modifySlice(s []int) {
    s[0] = 100
}

func main() {
    a := []int{1, 2, 3}
    modifySlice(a)
    fmt.Println(a) // 输出 [100 2 3],原切片被改变
}

并发场景下选择及避免数据竞争、提高性能

  1. 生产者 - 消费者模型:在生产者 - 消费者模型中,如果数据量相对固定且已知,对性能要求极高,且不需要动态扩容,可考虑使用数组。但由于数组是值类型,在并发读写时,为避免数据竞争,可使用互斥锁(sync.Mutex)。例如:
package main

import (
    "fmt"
    "sync"
)

var mu sync.Mutex
var arr [10]int
var index int

func producer(wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 5; i++ {
        mu.Lock()
        arr[index] = i
        index++
        mu.Unlock()
    }
}

func consumer(wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 5; i++ {
        mu.Lock()
        fmt.Println(arr[index - 1])
        index--
        mu.Unlock()
    }
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go producer(&wg)
    go consumer(&wg)
    wg.Wait()
}
  1. 如果数据量动态变化:更适合使用切片。切片虽然是引用类型,但并发读写仍可能产生数据竞争,同样可使用互斥锁。另外,还可使用 sync.Map(Go 1.9 引入)或 channel 来避免数据竞争。例如,使用 channel 实现生产者 - 消费者模型:
package main

import (
    "fmt"
)

func producer(ch chan int) {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)
}

func consumer(ch chan int) {
    for val := range ch {
        fmt.Println(val)
    }
}

func main() {
    ch := make(chan int)
    go producer(ch)
    go consumer(ch)
    select {}
}
  • 使用 channel 天然地避免了数据竞争,并且在并发性能上更优,因为 channel 实现了同步通信,数据在 channel 传递时是线程安全的。相比之下,使用互斥锁在高并发时可能会出现锁争用问题,降低性能。所以在并发编程中,优先考虑使用 channel 来实现数据传递和同步,在一些特定场景(如需要对数据结构进行复杂读写操作且不适合使用 channel 时)再考虑使用互斥锁来保护共享数据。