深入硬件讲透go语言SDK中atomic.Load变量与atomic.Store变量(atomic.LoadInt32()与atomic.StoreInt32()函数)

概要

  实际工作的过程中发现很多小伙伴不是太清楚go语言SDK atomic包下Load变量与Store变量使用场景,已经用法,其实还是不是太了解计算机硬件相关的知识来深入理解SDK设计该方法的初衷

计算机硬件内存

  程序执行时,主要是CPU执行程序中的指令,指令运行还需要加载相应的数据;CPU运行的速度很快,如果每次使用IO总线访问内存获取CPU执行所需要的数据,无法将CPU的性能优势发挥到最大;
  数据从磁盘读取,加入到RAM主存,线程执行的时候会从CPU缓存获取数据,如果获取不到则把数据从RAM主存加载到CPU缓存中,直接从CPU缓存获取数据可以尽可能的发挥CPU最大性能优势
  CPU缓存分为L1、L2、L3,3个级别的缓存,L1级别的缓存就是常说的寄存器缓存,如下图所示
任务管理器

在这里插入图片描述

在这里插入图片描述
  如上图 工作线程/本地线程相当于CPU中的L1/L2/L3 缓存,主内存相当于RAM内存条,就是我们买电脑所说的电脑内存配置,从图中可以看出如果多个线程对同一个共享变量进行操作会存在线程不安全的问题,比如变量a=1,此时A、B两个线程都把a=1读入内存,其中A线程修改变量a=2,此时B线程再次读取a变量,可能a的值还是a=1,这个时候程序就会出现bug,如果是单线程操作一个变量就不会出现这种问题
  atomic.Load变量与atomic.Store变量就是解决上述线程不安全问题,类似于Java用volatile关键字来保证并发的可见性(volatile实现内存可见性是通过store和load指令完成的),只不过Java是通过语言层面来解决的,使用起来会更加方便一些,以atomic.LoadInt32()与atomic.StoreInt32()函数为例:

  • ** atomic.LoadInt32() **就是原子的把类型为int32的变量从主内存加载到工作内存,防止并发情况下的脏读
  • **atomic.StoreInt32()**就是原子的把类型为int32的变量从工作内存同步到主内存,防止其他线程读取到不正确的数据

示例问题代码

package main

import (
	"fmt"
)

var x int32 = 1

func storeFunc() {
	for i := 0; ; i++ {
		if i%2 == 0 {
			x = 2
		} else {
			x = 3
		}
	}
}

func main() {
	go storeFunc()
	for {
		fmt.Printf("%x\n", x)
	}
}


输出结果:

1
1
1
1
1
1
1
1
1
1
1
1
1

结果一直输出1,可以看出这里输出的是错误的脏数据,实际上变量已经变成2或者3了

示例正确代码

package main

import (
	"fmt"
	"sync/atomic"
)

var x int32 = 1

func storeFunc() {
	for i := 0; ; i++ {
		if i%2 == 0 {
			atomic.StoreInt32(&x, 2)
		} else {
			atomic.StoreInt32(&x, 3)
		}
	}
}

func main() {
	go storeFunc()
	for {
		fmt.Printf("%x\n", atomic.LoadInt32(&x))
	}
}

输出结果:

2
2
2
3
3
2
3
2
2
3
2
3
2

输出结果可以看出解决了协程可见性的问题

协程的可见性

  讲到这里可能有同学会说go语言使用的是协程,上面一直再说线程,两个是不同的概念,实际上道理是一样的
在这里插入图片描述
  上图可以看出一个线程会间歇执行多个协程(协程的切换时间片是10ms),单个线程执行的多个协程是间歇串行执行的关系,没有并发,而且协程是共享线程的工作内存的,所以单个线程的协程之间不会有可见性问题发生,但是分布在不同线程的协程就不会共享线程的工作内存,他们之间就会有线程不安全,可见性等问题发生
  当然go语言协程的GMP模型比上图概念以及工作机制要复杂的多,这里不做深入讲解,但是理解线程不安全以及可见性跟线程是大同小异的

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
std::atomic_load是C++标准库的一个函数,用于从一个std::atomic对象加载(读取)值。它的作用是读取std::atomic对象的值,该对象可以保证在多线程环境下的原子性操作。 std::atomic_load_explicit是std::atomic_load的一个变体,它允许用户指定一个memory_order参数,以控制内存访问的顺序和一致性。 在C++的atomic,除了提供基本的atomic类型外,还提供了两个模板算法atomic_loadatomic_store,它们可以用来对任何类型进行atomic操作。atomic_load用于加载(读取)值,而atomic_store用于存储(写入)值。 在C++11标准,还提供了std::atomic_compare_exchange_strong函数,它实现了CAS(Compare-And-Swap)算法。CAS是一种并发算法,用于在多线程环境下实现原子操作。CAS操作包括三个操作数:一个内存位置、一个期望值和一个新值。它的作用是比较内存位置的当前值与期望值是否相等,如果相等,则将新值写入该内存位置,否则不做任何操作。 在上面的示例代码,使用std::atomic_compare_exchange_strong函数实现了一个increment函数,用于对std::atomic<int>类型的data对象进行原子加一操作。首先,函数使用data.load()读取data的当前值,然后使用compare_exchange_strong函数进行CAS操作,如果比较和交换成功,则将expected加一,否则重新加载data的值,直到操作成功为止。这样可以确保在多线程环境下对data对象进行原子加一操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [[moder c++] atomic_loadatomic_store](https://blog.csdn.net/ykun089/article/details/125820270)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [std:: atomic::load | 无锁结构](https://blog.csdn.net/m0_48739934/article/details/129183795)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王柳敬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值