go并发编程之二(原子性)

原子性相关的概念


我们知道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 必须放在文件名称的前面,该选项用来检测运行时是否有数据竞争

猜你喜欢

转载自blog.csdn.net/wujiangwei567/article/details/86680998
今日推荐