Go语言数组特性
- 固定长度:声明时需要指定长度,且长度不可变。例如
var arr [5]int
,长度为5,后续不能改变数组长度。
- 值类型:数组作为参数传递时,是值传递,会完整复制一份数组。例如:
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语言切片特性
- 动态长度:切片是基于数组的动态数据结构,长度可变。通过
make
函数创建,如 s := make([]int, 0, 10)
,初始长度为0,容量为10,随着元素添加,长度和容量可动态变化。
- 引用类型:切片作为参数传递时,传递的是切片头信息(包含指向底层数组的指针、长度和容量),对切片的修改会影响到原切片。例如:
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],原切片被改变
}
并发场景下选择及避免数据竞争、提高性能
- 生产者 - 消费者模型:在生产者 - 消费者模型中,如果数据量相对固定且已知,对性能要求极高,且不需要动态扩容,可考虑使用数组。但由于数组是值类型,在并发读写时,为避免数据竞争,可使用互斥锁(
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()
}
- 如果数据量动态变化:更适合使用切片。切片虽然是引用类型,但并发读写仍可能产生数据竞争,同样可使用互斥锁。另外,还可使用
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
时)再考虑使用互斥锁来保护共享数据。