原子性相关的概念
我们知道redis操作是原子性的,究其具体原因是因为,redis服务是单线程的,当多个客户端发送命令
到服务端执行时,所有的命令会进入队列,依次执行,如下图:
1. 什么是原子性
原子性是指的某种东西不可分割或者不可中断的一种属性。
在考虑原子性时,经常需要做的第一件事是定义上下文或作用域,这个操作将被视为原子性的。这是思考
程序的基础
让我们看一下术语“不可分割”和“不可中断”。这些术语意味着在你定义的上下文中,原子的东西
将在整个过程中发生,而不会同时发生任何事情。 这让人有点糊涂,所以我们来看一个例子:
i++
这是一个任何人都可以明白的简单代码,但它很容易证明原子性的概念。 它可能看起来很原子,但是一个
简单的分析揭示了几种操作:
. 检索i的值。
. 增加i的价值。
. 存储i的值。
尽管这些操作中的每一个都是原子的,但三者的组合可能不是,取决于你的上下文。
当变量i的作用域仅局限于某个函数,且外部环境无法改变i的值时,我们亦可以说 i++
是一个原子操作
2. 我们为什么需要原子性
假设一个秒杀场景,我们的商品数量goods_num是10,现在100个人来并发抢购,goods_num 的加减读取
操作如果不是原子操作,那么很可能 100 个人同时操作了库存,导致库存数量变为-90,防止并发资源的争抢
就是我们接下来要说的同步。
3. 使用原子操作
我们可以使用原子操作包
atomic
解决对常见单个变量的增减操作问题
/**
减少库存操作
*/
func decStock(stock *int32,wg *sync.WaitGroup) {
atomic.AddInt32(stock,-1)
//*stock-- 会出现竞争问题
defer wg.Done()
}
func main() {
var num int32 = 10
var wg sync.WaitGroup
wg.Add(10)
for i :=0;i<10;i++ {
go decStock(&num,&wg)
}
wg.Wait()
fmt.Println(num)
}
使用命令:
go run --race main.go
注意
--race
必须放在文件名称的前面,该选项用来检测运行时是否有数据竞争