go的slice学习

并发访问slice

线上出现一粒多协程并发append全局slice的情况,导致内存不断翻倍,因此对slice的使用需要重新考虑。

并发读写的情况下, 可以利用锁、channel等避免竞态

问题

go 复制代码
func TestDemo32(t *testing.T) {
	var wg sync.WaitGroup
	var n = 100
	s := make([]int, 0, 200)
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Printf("Data addr: %d\n", hdr.Data)
	fmt.Printf("Data cap: %d\n", hdr.Cap)
	fmt.Printf("Data len: %d\n", hdr.Len)
	wg.Add(n)
	for i := 1; i <= n; i++ {
		go func(v int) {
			defer wg.Done()
			s = append(s, v)
		}(i)
	}
	wg.Wait()
	hdr = (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Printf("Data addr: %d\n", hdr.Data)
	fmt.Printf("Data cap: %d\n", hdr.Cap)
	fmt.Printf("Data len: %d\n", hdr.Len)
	fmt.Println(jsonx.ToString(s))

	// Data addr: 824645965056
	// Data cap: 200
	// Data len: 0
	// Data addr: 824645965056
	// Data cap: 200
	// Data len: 96
}

func TestDemo33(t *testing.T) {
	var wg sync.WaitGroup
	var n = 500
	s := make([]int, 0, 10)
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Printf("Data addr: %d\n", hdr.Data)
	fmt.Printf("Data cap: %d\n", hdr.Cap)
	fmt.Printf("Data len: %d\n", hdr.Len)
	wg.Add(n)
	for i := 1; i <= n; i++ {
		go func(v int) {
			defer wg.Done()
			s = append(s, v)
		}(i)
	}
	wg.Wait()
	hdr = (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Printf("Data addr: %d\n", hdr.Data)
	fmt.Printf("Data cap: %d\n", hdr.Cap)
	fmt.Printf("Data len: %d\n", hdr.Len)
	fmt.Println(jsonx.ToString(s))
	
	// Data addr: 824635459136
	// Data cap: 10
	// Data len: 0
	// Data addr: 824665328128
	// Data cap: 672
	// Data len: 453
}

Go语言中的slice是一种引用类型,它本身不保存任何元素,只是对数组的引用。

append操作的主要功能是向slice添加元素。

如果slice的底层数组容量足够,就直接添加;如果不够,就会先分配新的底层数组,然后将原有的元素和新添加的元素一起拷贝到新数组,在这个过程中,原有的底层数组会被垃圾回收。

当底层数组的容量不足以容纳新的元素时,就会产生新的底层数组

此时原有的slice和返回的新的slice,其底层数组是不同的。

这也是为什么在使用append时,总是习惯性地将结果再次赋值给原slice。

方案一 channel

go 复制代码
func TestDemo(t *testing.T) {
	// 无缓冲,发送侧有数据,接收侧才执行
	// 用于做同步
	c := make(chan struct{})

	// new 了该 job 后,该 job 就开始准备从 channel 接收数据
	s := NewScheduleJob(n, func() { c <- struct{}{} })

	// 并发发送数据到channel
	var wg sync.WaitGroup
	var n  = 1000
	wg.Add(n)
	for i := 0; i < n; i++ {
		go func(v int) {
			defer wg.Done()
			s.AddData(v)
		}(i)
	}

	// 等待上述多个协程将数据存入slice
	wg.Wait()

	// 发送完之后关闭channel
	s.Close()

	// 阻塞在这里是等待NewScheduleJob执行结束
	<-c
	
	// 最终实现读写一致
	fmt.Println(len(s.data))
}

type ServiceData struct {
	ch   chan int // 用来同步的channel
	data []int    // 存储数据的slice
}

// Schedule 从 channel 接收数据串行存入slice,直到ch关闭
func (s *ServiceData) Schedule() {
	for i := range s.ch {
		s.data = append(s.data, i)
	}
}

// Close 关闭channel
func (s *ServiceData) Close() {
	close(s.ch)
}

// AddData 发送数据到 channel
func (s *ServiceData) AddData(v int) {
	s.ch <- v
}

func NewScheduleJob(size int, done func()) *ServiceData {
	s := &ServiceData{
		ch:   make(chan int, size),
		data: make([]int, 0),
	}

	go func() {
		// 并发地 append 数据到 slice
		// Schedule 从 channel 接收数据串行存入slice,直到ch关闭
		s.Schedule()
		// 通知主协程继续执行
		done()
	}()

	return s
}

优点:逻辑复杂,适用于高并发场景

方案二 - 锁

go 复制代码
func main() {
	slc := make([]int, 0, 1000)
	var wg sync.WaitGroup
	var lock sync.Mutex

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func(a int) {
			defer wg.Done()
			lock.Lock()
			defer lock.Unlock()
			slc = append(slc, a)
		}(i)
	}
   wg.Wait()
	fmt.Println(len(slc))
}

优点:锁的逻辑重,适用于对性能要求不高的场景

学习文档

https://juejin.cn/post/6844904134592692231

相关推荐
半盏茶香5 分钟前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
Evand J1 小时前
LOS/NLOS环境建模与三维TOA定位,MATLAB仿真程序,可自定义锚点数量和轨迹点长度
开发语言·matlab
LucianaiB1 小时前
探索CSDN博客数据:使用Python爬虫技术
开发语言·爬虫·python
Ronin3051 小时前
11.vector的介绍及模拟实现
开发语言·c++
计算机学长大白2 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言
PieroPc3 小时前
Python 写的 智慧记 进销存 辅助 程序 导入导出 excel 可打印
开发语言·python·excel
2401_857439696 小时前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna6 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
xlsw_6 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
Dream_Snowar7 小时前
速通Python 第三节
开发语言·python