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

Golang实例讲解,数字递增的线程安全性问题

原创
作者头像
一凡sir
修改于 2023-07-23 01:32:51
修改于 2023-07-23 01:32:51
4780
举报
文章被收录于专栏:技术成长技术成长

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

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

import (
   "sync"
   "time"
   "fmt"
   "sync/atomic"
)

var data1 int = 0
var data2 *int32
var wgInt sync.WaitGroup = sync.WaitGroup{}

func main() {
   t := int32(0)
   data2 = &t
   max := 100000
   wgInt.Add(max)
   fmt.Printf("data1 add num=%d\n", max)
   time1 := time.Now().UnixNano()
   for i := 0; i < max; i++ {
      go addData1()
   }
   wgInt.Wait()
   time2 := time.Now().UnixNano()
   fmt.Printf("data1=%d, time=%d ms\n", data1, (time2-time1)/1000000)

   wgInt.Add(max)
   fmt.Printf("data2 add num=%d\n", max)
   time3 := time.Now().UnixNano()
   for i := 0; i < max; i++ {
      go addData2()
   }
   wgInt.Wait()
   time4 := time.Now().UnixNano()
   fmt.Printf("data2=%d, time=%d ms\n", *data2, (time4-time3)/1000000)
}
// 简单的+1处理,线程不安全
func addData1() {
   data1++
   wgInt.Done()
}
// 原子性+1处理,线程安全
func addData2() {
   atomic.AddInt32(data2, 1)
   wgInt.Done()
}

实例中定义了两个数字data1, data2,一个是普通的int类型,一个是int32指针,data1用简单的++运算符递增,data2用atomic.AddInt32()方法递增

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

代码语言:shell
AI代码解释
复制
data1 add num=100000

data1=98626, time=22 ms

data2 add num=100000

data2=100000, time=26 ms

为什么这么简单的++递增是不安全的呢?

这时,我们就需要深入理解计算机原理了。

++运算符实际上是三个操作,从内存读取data1,cpu更新data1=data1+1,写入data1到内存。

由于我们是多核并行运算的,那么从读取到写入整个过程中,就会出现不同的cpu内核读取到相同的数值,然后更新同样的数值写入到内存,这样就造成++数量比预期的少。

那么atomic.AddInt32()方法又是怎么保证数值递增的安全性呢?

这里就涉及到CAS( Compare and Swap )操作特定的一个复合指令cmpxchg,这个复合指令是现代CPU将上面的3条指令合成为一个指令,由CPU支持的一个复合指令,保证了操作的原子性。

并行运算时数据问题方面,CAS操作没有用互斥锁那么重,而使用的缓存锁的方式,在CPU高速缓存上对缓存行的一致性更新来保证数据更新的一致性

这里只是一个最简单的,单机同进程中,数字递增的并发处理,放大到分布式系统中,这种情况还会更加复杂,比如:如何安全的减少库存。

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

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

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Golang实例讲解,slice并发读写的线程安全性问题
本地计算机是4核i5处理器,并发运行1w个协程,看到下面的执行结果,和大家预期的一样吗?
一凡sir
2023/07/23
1.4K0
Go:goroutine线程池 ants 简介与实践
github地址: https://github.com/panjf2000/ants
Freedom123
2024/03/29
8470
Go:goroutine线程池 ants 简介与实践
Go实现海量日志收集系统(三)
再次整理了一下这个日志收集系统的框,如下图 这次要实现的代码的整体逻辑为: 完整代码地址为: https://github.com/pythonsite/logagent etcd介绍 高可用的分布式
coders
2018/05/28
1K0
Go基础之锁的初识
当我们的程序就一个线程的时候是不需要用到锁的,但是通常我们实际的代码不会是单个线程的,所有这个时候就需要用到锁了,那么关于锁的使用场景主要涉及到哪些呢? 当我们多个线程在读相同的数据的时候则是需要加锁的 当我们的程序既有读又有写的时候更是需要加锁的 当我们有多个线程在写的时候同样也是需要加锁 互斥锁 互斥锁:同一个时刻只有一个线程能够拿到锁 我们先通过一个例子来演示,如果当多个线程同时更改一个变量,结果会是怎么样 不加锁版本 package main import ( "sync" "fm
coders
2018/03/30
5380
读猿码系列——6.Golang中用幂等思路解决缓存击穿的方案:singleflight
今天我们来看一个日常工作中会遇到的问题:实际开发中常见的做法是在查数据库前先去查缓存,如果缓存Miss(未命中)就去数据库中查到数据并放到缓存里。这是正常情况,然而缓存击穿则是指在高并发系统中,大量请求同时查询一个缓存的key,假如这个key刚好过期就会导致大量的请求都打到数据库上。在绝大多数情况下,可以考虑使用singleflight来抑制重复函数调用。
才浅Coding攻略
2022/12/12
7110
victoriaMetrics中的一些Sao操作
victoriaMetrics中有一个fasttime库,用于快速获取当前的Unix时间,实现其实挺简单,就是在后台使用一个goroutine不断以1s为周期刷新表示当前时间的变量currentTimestamp,获取的时候直接原子加载该变量即可。其性能约是time.Now()的8倍。
charlieroro
2022/05/09
7480
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
3970
字节开源Go协程池 gopool
一般来说,用 waitGroup 结合 channel ,可以实现一个协程池的功能。一个协程池,一般要具有如下三个功能:
王小明_HIT
2024/04/17
2.2K0
字节开源Go协程池 gopool
Golang 并发赋值的安全性探讨
比如对一个变量简单的自增操作count++,在非并发下很好理解,而在并发情况下却容易出现预期之外的结果,这样的代码就是非并发安全的。
恋喵大鲤鱼
2021/04/08
9.4K3
推荐很好用的Goroutine连接池
ants是一个高性能的协程池,实现了对大规模goroutine的调度管理、goroutine复用,允许使用者在开发并发程序的时候限制协程数量,复用资源,达到更高效执行任务的效果。
李海彬
2018/07/26
1.4K0
推荐很好用的Goroutine连接池
Golang 基础:底层并发原语 Mutex RWMutex Cond WaitGroup Once等使用和基本实现
上一篇 《原生并发 goroutine channel 和 select 常见使用场景》 介绍了基于 CSP 模型的并发方式。
张拭心 shixinzhang
2022/05/10
4230
Golang 基础:底层并发原语 Mutex RWMutex Cond WaitGroup Once等使用和基本实现
go的并发小知识
从第3点钟的操作状态表中可以看到,我们有四种操作会导致goroutine阻塞,三种操作会导致程序panic!因此,为了尽可能转移这些风险,我们需要分配channel的所有权。即,channel的所有者做实例化、写入和关闭操作;channel的使用者做读取操作,且约束其他人无法对其做相应的操作。一个优雅的实现:
天地一小儒
2022/12/28
2340
go的并发小知识
Java程序员学习Go指南(三)
转载:https://www.luozhiyun.com/archives/213
luozhiyun
2020/02/18
3110
Java程序员学习Go指南(三)
聊聊gost的GoSafely
gost提供了GoSafely方法,它接收WaitGroup、ignoreRecover、handler、catchFunc参数,其大致的模板是,首先对WaitGroup进行add(1),然后一步执行带defer的handler。catchFunc算是一个mini版的GoSafely,先执行wg.Add(1),再异步执行func,异步func里头先注册defer,处理recover及wg,然后执行catchFunc。
code4it
2021/02/17
5210
聊聊gost的GoSafely
Go并发编程-并发编程难在哪里
编写正确的程序本身就不容易,编写正确的并发程序更是难中之难,那么并发编程究竟难道哪里那?本节我们就来一探究竟。
加多
2020/02/18
7120
Go并发编程-并发编程难在哪里
Golang | 优雅的计算接口耗时、接口限流以及接口超时处理思路
描述: Goglang 接口耗时监控测试用例 核心:使用 defer + 匿名函数 再加上 time.Since() 函数实现再程序结束完毕时计算此代码片段(接口)执行耗时 示例:
全栈工程师修炼指南
2023/10/31
1.2K0
Golang | 优雅的计算接口耗时、接口限流以及接口超时处理思路
Golang 五种原子性操作的用法详解
本文我们详细聊一下Go语言的原子操作的用法,啥是原子操作呢?顾名思义,原子操作就是具备原子性的操作... 是不是感觉说了跟没说一样,原子性的解释如下:
KevinYan
2021/09/24
3.8K1
Golang实例讲解,map并发读写的线程安全性问题
上面的代码中 var data mapintint 是一个key和value都是int类型的map,启动的协程并发执行时,也只是非常简单的对 datai=i 这样的一个赋值操作。
一凡sir
2023/07/21
6210
白话 Golang 协程池
并发指在一段时间内有多个任务(程序,线程,协程等)被同时执行。注意,不是同一时刻。
恋喵大鲤鱼
2021/05/18
2K0
白话 Golang 协程池
[警惕] 请勿滥用goroutine
在Go语言中,goroutine的创建成本很低,调度效率高,Go语言在设计时就是按以数万个goroutine为规范进行设计的,数十万个并不意外,但是goroutine在内存占用方面确实具有有限的成本,你不能创造无限数量的它们,比如这个例子:
Golang梦工厂
2022/07/11
5090
[警惕] 请勿滥用goroutine
相关推荐
Golang实例讲解,slice并发读写的线程安全性问题
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档