Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Golang实例讲解,slice并发读写的线程安全性问题

Golang实例讲解,slice并发读写的线程安全性问题

原创
作者头像
一凡sir
发布于 2023-07-23 00:25:13
发布于 2023-07-23 00:25:13
1.3K0
举报
文章被收录于专栏:技术成长技术成长

先上实例代码,后面再来详细讲解。

代码语言:go
AI代码解释
复制
/**
 * 并发编程,切片的线程安全性问题
 */
package main

import (
   "fmt"
   "sync"
   "time"
)

var list []int = []int{}
var wgList sync.WaitGroup = sync.WaitGroup{}
var muList sync.Mutex = sync.Mutex{}

func main() {
   // 并发启动的协程数量
   max := 10000
   fmt.Printf("list add num=%d\n", max)
   wgList.Add(max)
   time1 := time.Now().UnixNano()
   for i := 0; i < max; i++ {
      go addNotSafe()
   }
   wgList.Wait()
   time2 := time.Now().UnixNano()
   fmt.Printf("list len=%d, time=%d ms\n", len(list), (time2-time1)/1000000)

   // 覆盖后再执行一次
   list = []int{}
   fmt.Printf("new list add num=%d\n", max)
   wgList.Add(max)
   time3 := time.Now().UnixNano()
   for i := 0; i < max; i++ {
      go addSafe()
   }
   wgList.Wait()
   time4 := time.Now().UnixNano()
   fmt.Printf("new list len=%d, time=%d ms\n", len(list), (time4-time3)/1000000)
}

// 线程不安全的方法
func addNotSafe() {
   list = append(list, 1)
   wgList.Done()
}

// 线程安全的方法,增加了互斥锁
func addSafe() {
   muList.Lock()
   list = append(list, 1)
   muList.Unlock()
   wgList.Done()
}

上面代码中的 var list []int 就是我们这次验证的主角,slice。

主程序发起1w个并发,不断的往slice中填充数据。

不安全的方式,将新数据直接 append 到slice中。

安全的方式,需要在 append 之前加锁,然后操作完再解锁。

本地计算机是4核i5处理器,并发运行1w个协程,看到下面的执行结果,和大家预期的一样吗?

代码语言:shell
AI代码解释
复制
list add num=10000

list len=9989, time=2 ms

new list add num=10000

new list len=10000, time=2 ms

list加1w个数据,但是最后只看到9989个,不足1w个的原因就是因为线程不安全,造成数据的丢失。

那么,为什么会出现这样的线程安全性问题呢?

并发读写在单线程运行时就不会有这种线程安全性问题。

而现在多核CPU,多线程的程序,这种问题就会越来越突出。

我们来思考下:

线程A, list=append(list,1) ,这时候 list={1},那么新的list就是list={1,1}

线程B, list=append(list,1) ,这时候 list={1},那么新的list就是list={1,1}

发现了没有,线程A和线程B是同时运行(多核并行运算),而且拿到的list变量也是完全一样的值,那么各自计算之后,更新list的值也是完全一样。

不论是线程A先写入内存,还是线程B先写入内存,肯定就有一次写入会覆盖之前一次写入,最终的结果是list={1,1},而不是list={1,1,1}。

上面就是因为线程不安全,导致少写入了一个数据。

再看下加锁的情况下,为什么就安全了呢?

我们再来思考下:

线程A, lock, list=append(list,1), unlock ,这时候 list={1},那么新的list就是 list={1,1}

线程B, lock/等待, list=append(list,1), unlock , 这时候 list={1,1},那么新的list就是 list={1,1,1}

因为append的前后有一个加锁、解锁的指令,这样就避免了多线程同时并行执行 list=append(list,1) 的操作。

不存在并行运算,那么并发操作也就是安全了。

关于并行、并发的概念,大家可以参考之前的系列文章。

这里保证 slice 线程安全的方法是用互斥锁,也可以考虑把数据写入、更新的代码封装到一个 channel 中,有一个专门的协程来单独维护 slice 的数据更新。如:

代码语言:go
AI代码解释
复制
for {

    data := <- chanList

    list = append(list, data)

}

由于 slice 不存在并发读写的冲突,所以在读取的时候可以省去加锁的操作,也就不用考虑读写锁了。

后面的文章,我们再来一起看下map的线程安全性问题,跟slice还是有很大不同哟。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Golang实例讲解,map并发读写的线程安全性问题
上面的代码中 var data mapintint 是一个key和value都是int类型的map,启动的协程并发执行时,也只是非常简单的对 datai=i 这样的一个赋值操作。
一凡sir
2023/07/21
6090
Golang实例讲解,数字递增的线程安全性问题
实例中定义了两个数字data1, data2,一个是普通的int类型,一个是int32指针,data1用简单的++运算符递增,data2用atomic.AddInt32()方法递增。
一凡sir
2023/07/22
4670
Golang中slice和map的线程安全问题
多个线程在并行访问同一个对象时,线程安全的代码通过同步机制保证各个线程都可以正常且正确的执行,且都可以获得正确的结果,不会出现数据污染等情况,就表示这个对象是线程安全的。
素履coder
2022/05/23
3.4K0
Go语言入门(八)线程安全&锁
线程安全&锁 定时器&一次性定时器 定时器 func main() { ticker := time.NewTicker(time.Second) //ticker.C是一个只读的chan,所以直接可以使用for range读取 for v := range ticker.C { fmt.Printf("hello %v\n",v) //按秒输出 } } 一次性定时器 func main() { select { case <- time.After(ti
alexhuiwang
2020/09/24
3930
Go的append操作是线程安全的吗
“ 根据golang中slice的数据结构可知,slice依托数组实现,在底层数组容量充足时,append操作不是只读操作,会将元素直接加入数组的空闲位置。因此,在多协程 对全局slice进行append操作时,会操作同一个底层数据,导致读写冲突”
Go学堂
2023/01/31
1.3K0
golang学习笔记——sync库
time.Sleep(time.Millisecond * time.Duration(rand.Intn(3)))
码缘
2021/03/09
3810
并发编程,为什么选Go?
导语 | 代码的稳健、可读和高效是我们每一个coder的共同追求。本文将结合Go语言特性,为书写高效的代码,力争从并发方面给出相关建议。让我们一起学习Go高性能编程的技法吧~ 在上篇《再不Go就来不及了!Go高性能编程技法解读》中我们结合Go语言特性,为书写高效的代码,从常用数据结构、内存管理两个方面给出相关建议,本篇将深入并发这部分进行阐述。 一、并发编程 (一)关于锁 无锁化 加锁是为了避免在并发环境下,同时访问共享资源产生的安全问题。那么,在并发环境下,是否必须加锁?答案是否定的。并非所有的并发都需要
腾讯云开发者
2022/03/30
6870
面试官:map为什么是非线程安全的?
这就是矛与盾的关系,go 语言的设计者认为,在大部分场景中,对 map 的操作都非线程安全的;
小锟哥哥
2022/05/10
1.7K0
面试官:map为什么是非线程安全的?
【Golang】并发
go 程(goroutine)是 go 并发的核心,它比线程要更小, 由 go Runtime 管理,运行 goroutine 只需要很少的栈空间,因此可以实现很大的并发量,在 go 中,开启一个 goroutine 只需要使用 go 关键字即可:
JuneBao
2022/10/26
4590
Golang 并发赋值的安全性探讨
比如对一个变量简单的自增操作count++,在非并发下很好理解,而在并发情况下却容易出现预期之外的结果,这样的代码就是非并发安全的。
恋喵大鲤鱼
2021/04/08
9.4K3
Golang多线程简单斗地主
哈哈这个程序的精髓是,由于时(lan)间(de)有(xie)限(le),打牌是哪个线程抢到了就出牌,直到牌出完了,就赢了。(多线程写斗地主,是我大学操作系统课程的实验项目,当时是完整实现了斗地主算法的,用的是C++和MFC,可以在界面上交互打牌)
dongfanger
2020/09/23
7490
Go 语言并发编程系列(十)—— sync 包系列:互斥锁和读写锁
我们前面反复强调,在 Go 语言并发编程中,倡导「使用通信共享内存,不要使用共享内存通信」,而这个通信的媒介就是我们前面花大量篇幅介绍的通道(Channel),通道是线程安全的,不需要考虑数据冲突问题,面对并发问题,我们始终应该优先考虑使用通道,它是 first class 级别的,但是纵使有主角光环加持,通道也不是万能的,它也需要配角,这也是共享内存存在的价值,其他语言中主流的并发编程都是通过共享内存实现的,共享内存必然涉及并发过程中的共享数据冲突问题,而为了解决数据冲突问题,Go 语言沿袭了传统的并发编程解决方案 —— 锁机制,这些锁都位于 sync 包中。
学院君
2019/09/10
9020
(四十二)golang--协程之间通信的方式
计算1-200之间各个数的阶乘,并将每个结果保存在mao中,最终显示出来,要求使用goroutime。
西西嘛呦
2020/08/26
1.2K0
白话 Golang 协程池
并发指在一段时间内有多个任务(程序,线程,协程等)被同时执行。注意,不是同一时刻。
恋喵大鲤鱼
2021/05/18
2K0
白话 Golang 协程池
Golang中slice和map并发写入问题解决
本篇文章为大家分享在Golang中,如何实现对slice和map两种数据类型进行并发写入。对于入门Golang的开发者来说,可能无法意识到这个问题,这里也会做一个问题演示。
兔云小新LM
2022/06/08
4.2K0
Golang中slice和map并发写入问题解决
Go语言多线程爬虫与代理IP反爬
有个朋友想用Go语言编写一个多线程爬虫,并且使用代理IP来应对反爬措施。多线程在Go中通常是通过goroutine实现的,所以应该使用goroutine来并发处理多个网页的抓取。然后,代理IP的话,可能需要一个代理池,从中随机选择代理来发送请求,避免同一个IP被封锁。大体思路就是这样,具体看我下面实操吧。
华科云商小徐
2025/05/12
730
Go 高性能编程技法
作者:dablelv,腾讯 IEGggG 后台开发工程师 代码的稳健、可读和高效是我们每一个 coder 的共同追求。本文将结合 Go 语言特性,为书写效率更高的代码,从常用数据结构、内存管理和并发,三个方面给出相关建议。话不多说,让我们一起学习 Go 高性能编程的技法吧。 常用数据结构 1.反射虽好,切莫贪杯 标准库 reflect 为 Go 语言提供了运行时动态获取对象的类型和值以及动态创建对象的能力。反射可以帮助抽象和简化代码,提高开发效率。 Go 语言标准库以及很多开源软件中都使用了 Go 语言的反
腾讯技术工程官方号
2022/03/18
2.1K0
Go编写工具教程第一课 高并发端口扫描
今天我们一起来学习下如何用GO 编写一个高并发端口扫描工具,本教学文章持续连载,后面会接连着实现主机发现,漏洞探测,远程执行,暴力破解等等的教学,有兴趣的师傅可关注公众号回复加群一起讨论~
WgpSec
2021/02/04
2.5K0
Go编写工具教程第一课 高并发端口扫描
(四十三)golang--管道
计算1-200之间各个数的阶乘,并将每个结果保存在map中,最终显示出来,要求使用goroutine。
西西嘛呦
2020/08/26
5460
盘点Golang并发那些事儿之二
上一节提到,golang中直接使用关键字go创建goroutine,无法满足我们的需求。主要问题如下
PayneWu
2021/06/10
5120
盘点Golang并发那些事儿之二
相关推荐
Golang实例讲解,map并发读写的线程安全性问题
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档