数组适用场景
- 固定大小数据存储:当你确切知道需要存储的数据数量,且数量在程序运行期间不会改变时,适合使用数组。例如,一个存储一年12个月销售额的数组,因为月份数量固定不变。
- 底层数据结构:在一些对性能要求极高且数据量固定的底层算法实现中,数组能提供简洁高效的存储方式。比如在某些图形算法中固定大小的点坐标数组。
切片适用场景
- 动态数据存储:当你不确定数据的最终数量,或者数据量在程序运行过程中会频繁变化时,切片是更好的选择。例如,从文件中读取数据行,你不知道文件具体行数,就可以使用切片来动态存储。
- 灵活的数据操作:切片支持灵活的追加、删除、截取等操作,适用于需要对数据集合进行各种动态调整的场景。比如实现一个简单的栈或队列数据结构,使用切片方便进行入栈、出栈、入队、出队操作。
使用数组并发操作的陷阱及规避方法
- 陷阱:
- 数据竞争:多个协程同时读写数组相同位置的数据,可能导致数据不一致。例如,两个协程同时对数组的某个元素进行累加操作,可能会丢失更新。
- 数组大小固定:在并发环境下,如果需要动态扩展数组大小,会变得非常困难,因为数组大小在声明时就已固定。
- 规避方法:
- 使用互斥锁(Mutex):通过在读写数组操作前后加锁,保证同一时间只有一个协程能访问数组。示例代码如下:
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var arr [10]int
func updateArray(index, value int, wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
arr[index] = value
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go updateArray(i, i*2, &wg)
}
wg.Wait()
fmt.Println(arr)
}
- **考虑使用其他数据结构**:如果需要动态大小,优先考虑切片等更灵活的数据结构。
使用切片并发操作的陷阱及规避方法
- 陷阱:
- 数据竞争:同数组一样,多个协程同时读写切片数据可能导致数据不一致。例如,多个协程同时向切片追加元素,可能导致切片内部结构损坏。
- 切片扩容问题:在并发环境下,切片扩容可能导致意想不到的结果。因为扩容涉及内存重新分配和数据复制,多个协程同时触发扩容可能导致数据丢失或重复。
- 规避方法:
- 使用互斥锁(Mutex):与数组类似,通过加锁保护切片操作。示例代码如下:
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var s []int
func appendToSlice(value int, wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
s = append(s, value)
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go appendToSlice(i*2, &wg)
}
wg.Wait()
fmt.Println(s)
}
- **使用通道(Channel)**:利用通道的特性来安全地在协程间传递数据,避免直接在多个协程中操作切片。例如,通过一个通道接收数据,然后在一个单独的协程中从通道读取数据并追加到切片。示例代码如下:
package main
import (
"fmt"
"sync"
)
func producer(ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 10; i++ {
ch <- i * 2
}
close(ch)
}
func consumer(ch chan int, wg *sync.WaitGroup) []int {
defer wg.Done()
var s []int
for value := range ch {
s = append(s, value)
}
return s
}
func main() {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(2)
go producer(ch, &wg)
go func() {
result := consumer(ch, &wg)
fmt.Println(result)
}()
wg.Wait()
}