网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
数据竞争:slice底层的结构体包含一个指向底层数组的指针和该数组的长度,当多个协程并发访问同一个slice时,有可能会出现数据竞争的问题。例如,一个协程在修改slice的长度,而另一个协程同时在读取或修改slice的内容。
内存重分配:在向slice中追加元素时,可能会触发slice的扩容操作,在这个过程中,如果有其他协程访问了slice,就会导致指向底层数组的指针出现异常。
二、并发场景
多个协程同时向 slice 追加元素,会有一部分元素被追加到了旧的底层数组里,最终 slice 的长度小于目标值。
func main() {
a := make([]int, 0)
for i := 0; i < 10000; i++ {
go func(i int) {
a = append(a, i)
}(i)
}
fmt.Println(len(a)) // 9015 < 10000
}
三、实现 slice 并发安全
要实现 slice 并发安全,有两种方法:加互斥锁、使用channel串行化操作。
方式一:使用互斥锁 sync.Mutex
追加元素之前调用 Lock() 函数加锁,追加完后,调用 Unlock() 解锁。
func main() {
var lock sync.Mutex //互斥锁
a := make([]int, 0)
var wg sync.WaitGroup
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
lock.Lock()
defer lock.Unlock()
a = append(a, i)
}(i)
}
wg.Wait()
fmt.Println(len(a))
// equal 10000
}
最终 slice 的长度等于目标值。
方式二:使用channel串行化操作
生产者生产元素,发送到通道中,消费者从通道中接收元素,追加到 slice 中。使用无缓冲通道,接收方、发送方必须同时存在,负责任意一方都会阻塞。
func main() {
buffer := make(chan int)
a := make([]int, 0)
// 消费者
go func() {
for v := range buffer {
a = append(a, v)
}
}()
// 生产者
var wg sync.WaitGroup
![img](https://img-blog.csdnimg.cn/img_convert/6f667bf97bc78985322757987504c34e.png)
![img](https://img-blog.csdnimg.cn/img_convert/a65643039209eb2199d0846a042c5c42.png)
![img](https://img-blog.csdnimg.cn/img_convert/960a1372466bf2066afa4f7334f8378a.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**
全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**