探索Go语言的原子操作秘籍:sync/atomic.Value全解析

引言

​ 在并发编程的世界里,数据的一致性和线程安全是永恒的话题。Go语言以其独特的并发模型——goroutine和channel,简化了并发编程的复杂性。然而,在某些场景下,我们仍然需要一种机制来保证操作的原子性。这就是sync/atomic.Value发挥作用的地方。

原子性:并发编程的基石

原子性(atomicity) 是指一个或多个操作在执行过程中不会被中断的特性。这些操作要么全部完成,要么全部不执行,从而避免了中间状态的暴露。在Go中,sync/atomic包提供了一组原子操作,而Value类型则是一种特殊的原子操作,用于存储和读取单个值。

适用场景:读多写少的优化

sync/atomic.Value利用了写时复制(Copy-On-Write,COW)技术,这使得它在读多写少的场景下表现卓越。由于COW的特性,频繁的读操作不需要加锁,而写操作则会产生一个新的副本,这在内存使用上可能不是最经济的,尤其是在内存较大且写操作频繁的情况下。

官方案例:配置信息的动态更新

​ 让我们通过一个官方示例来了解Value的使用。这个示例展示了如何使用Value来动态更新和读取服务器配置:

package mainimport ("sync/atomic""time"
)func loadConfig() map[string]string {return make(map[string]string)
}func requests() chan int {return make(chan int)
}func main() {var config atomic.Value // holds current server configuration// Create initial config value and store into config.config.Store(loadConfig())go func() {// Reload config every 10 seconds// and update config value with the new version.for {time.Sleep(10 * time.Second)config.Store(loadConfig())}}()// Create worker goroutines that handle incoming requests// using the latest config value.for i := 0; i < 10; i++ {go func() {for r := range requests() {c := config.Load()// Handle request r using config c._, _ = r, c}}()}
}
原理解析:Value的内部机制

Value的内部实现基于Go的interface{}类型,通过unsafe包来实现原子操作。下面是Value的定义和写入操作的核心逻辑:

// A Value provides an atomic load and store of a consistently typed value.
// The zero value for a Value returns nil from Load.
// Once Store has been called, a Value must not be copied.
//
// A Value must not be copied after first use.
type Value struct {v any
}

​ Value 的底层是一个 intreface 结构体类型,包含一个 interface 类型 v

// efaceWords is interface{} internal representation.
type efaceWords struct {typ  unsafe.Pointerdata unsafe.Pointer
}

​ efaceWords 是 interface 类型的内部实现,包含类型和值

写入操作

​ 写入操作的关键在于确保类型一致性和原子性。首次写入时,会禁用抢占,确保写入过程不会被中断。后续写入则会检查类型一致性,并原子性地更新数据。

var firstStoreInProgress byte// Store sets the value of the Value v to val.
// All calls to Store for a given Value must use values of the same concrete type.
// Store of an inconsistent type panics, as does Store(nil).
func (v *Value) Store(val any) {if val == nil {panic("sync/atomic: store of nil value into Value")}vp := (*efaceWords)(unsafe.Pointer(v))vlp := (*efaceWords)(unsafe.Pointer(&val))for {typ := LoadPointer(&vp.typ)if typ == nil {// Attempt to start first store.// Disable preemption so that other goroutines can use// active spin wait to wait for completion.runtime_procPin()if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(&firstStoreInProgress)) {runtime_procUnpin()continue}// Complete first store.StorePointer(&vp.data, vlp.data)StorePointer(&vp.typ, vlp.typ)runtime_procUnpin()return}if typ == unsafe.Pointer(&firstStoreInProgress) {// First store in progress. Wait.// Since we disable preemption around the first store,// we can wait with active spinning.continue}// First store completed. Check type and overwrite data.if typ != vlp.typ {panic("sync/atomic: store of inconsistently typed value into Value")}StorePointer(&vp.data, vlp.data)return}
}

流程:

  1. 如果传入的值为空会产生一个 panic

  2. 通过 unsafe.Pointer 将old value 和 new value 转化成 efaceWords 类型

  3. 进入 for 循环

  4. 如果 typ == nil 说明是第一次写入值,那么进入到第一次赋值的流程

    1. runtime_procPin() 禁止抢占,标记当前G在M上不会被抢占
    2. 使用 CompareAndSwapPointer 先尝试将typ设置为^uintptr(0)这个中间状态。如果失败,则证明已经有别的线程抢先完成了赋值操作,那它就解除抢占锁,然后重新回到 for 循环第一步。
    3. 如果设置成功则进入赋值阶段,注意这里是 先赋值 data,再赋值 typ,因为我们是根据 typ 是否等于 nil 判断 对象是否被初始化,所以最后赋值 typ 才能确保对象完成了初始化。
  5. 如果 typ 不等于 nil

    1. typ == unsafe.Pointer(&firstStoreInProgress) 判断初始化是否完成,未完成则回到 for 循环起始处

    2. 如果初始化对象完成,判断 typ != vlp.typ ,如果新写入的值不等于旧值则panic

    3. StorePointer(&vp.data, vlp.data) 把 old value 原子性替换成 new value

    4. // StorePointer atomically stores val into *addr.
      // Consider using the more ergonomic and less error-prone [Pointer.Store] instead.
      func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
      
读取操作

​ 读取操作相对简单,它会原子性地获取当前值。如果值尚未初始化,将返回nil

// Load returns the value set by the most recent Store.
// It returns nil if there has been no call to Store for this Value.
func (v *Value) Load() (val any) {vp := (*efaceWords)(unsafe.Pointer(v))typ := LoadPointer(&vp.typ)if typ == nil || typ == unsafe.Pointer(&firstStoreInProgress) {// First store not yet completed.return nil}data := LoadPointer(&vp.data)vlp := (*efaceWords)(unsafe.Pointer(&val))vlp.typ = typvlp.data = datareturn
}

流程:

  1. 首先载入 value

  2. if typ == nil || typ == unsafe.Pointer(&firstStoreInProgress) 判断写入过程是否初始化完成

  3. data := LoadPointer(&vp.data) 原子性载入 old value

// LoadPointer atomically loads *addr.
// Consider using the more ergonomic and less error-prone [Pointer.Load] instead.
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
  1. 定义一个新的值 vlp := (*efaceWords)(unsafe.Pointer(&val))
  2. 然后将 old value 赋值给 new value (COW 思想)
  3. 返回新的 value
    1. 所以每次调用 Load 我们都是获取到了一个副本,所以可以保证在并发读写时候的线程安全
总结与最佳实践

sync/atomic.Value是一个强大的工具,适用于需要高并发读取的场景。然而,它也有其局限性,特别是在内存使用和写入操作的频率上。在使用Value时,应当考虑以下几点:

  1. 读多写少Value最适合的场景是读操作远多于写操作。
  2. 内存效率:频繁的写入可能会因为COW机制导致内存使用增加。
  3. 类型安全:写入操作要求类型一致性,否则会引发panic
参考文献
  • Go sync/atomic包文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/15057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

对AI 感兴趣的小伙伴

如图&#xff0c;欢迎来玩儿&#xff01; 欢迎来玩儿

Python异常处理:打造你的代码防弹衣!

Hi&#xff0c;我是阿佑&#xff0c;上文咱们讲到——揭秘Python的魔法&#xff1a;装饰器的超能力大揭秘 ‍♂️✨&#xff0c;阿佑将带领大家通过精准捕获异常、使用with语句和上下文管理器、以及异常链等高级技巧来增强代码的健壮性。就像为代码穿上防弹衣&#xff0c;保护它…

生活小区火灾预警新篇章:泵吸式可燃气体报警器的检定与运用

在现代化的生活小区中&#xff0c;燃气设备广泛应用于居民的日常生活之中&#xff0c;但同时也带来了潜在的火灾风险。 可燃气体报警器作为一种安全监测设备&#xff0c;能够及时检测到燃气泄漏等安全隐患&#xff0c;并在达到预设的阈值时发出警报&#xff0c;提醒居民采取相…

SpringBoot Redis 扩展高级功能

环境&#xff1a;SpringBoot2.7.16 Redis6.2.1 1. Redis消息发布订阅 Spring Data 为 Redis 提供了专用的消息传递集成&#xff0c;其功能和命名与 Spring Framework 中的 JMS 集成类似。Redis 消息传递大致可分为两个功能区域&#xff1a; 信息发布 信息订阅 这是一个通常…

北斗短报文终端 | 什么是北斗短报文功能?如何实现北斗短报文通信?

北斗短报文功能是指通过北斗卫星进行短报文通信的功能。这种功能允许用户在没有移动通信信号覆盖的偏远山区、海洋、沙漠等地带&#xff0c;通过北斗短报文终端发送和接收文本信息&#xff0c;进行基本的数据通信。 北斗短报文功能是指北斗卫星导航系统特有的双向报文通信功能。…

HashMap和HashSet的详解

注意&#xff1a;HashMap和HashSet的常用方法和TreeMap和TreeSet是一样的&#xff0c;不过是他们实现的底层原理是不一样的&#xff0c;HashMap和HashSet的底层原理是哈希表结构&#xff0c;这种结构与搜索树或者红黑树来说效率更高&#xff0c;因此在平时使用是我们通常使用Ha…

urllib_post请求_百度翻译

打开百度翻译&#xff0c;并打开控制台&#xff0c;输入spider&#xff0c;然后在网络中找到对应的接口&#xff0c;可以看出&#xff0c;该url是post请求 在此案例中找到的接口为sug&#xff0c;依据为&#xff1a; 可以看到&#xff0c;传递的数据为kw : XXX&#xff0c; 所…

[Linux]服务管理

一.服务的概念&#xff0c;状态&#xff0c;查看系统服务 服务(service)本质就是进程 如(mysqld&#xff0c;sshd 防火墙等) 是运行在后台的&#xff0c;通常都会监听某个端口&#xff0c;等待其它程序的请求 -------比如mysqld&#xff0c;防火墙等&#xff0c;因此我们又称为…

【for循环解决问题】

for循环 #include<stdio.h> int main(){for(定义循环变量&#xff1b;循环次数&#xff1b;循环条件){//循环操作}return 0; } 我们用作业实践一下 作业&#xff1a; 输入4个整数 要求后三个数都小于第一个数 判断第四个数在不在中间两个数的范围内&#xff08;不包…

3D瓦片地图组件上线|提供DEM数据接入,全方位呈现三维地图地形!

在用户调研中&#xff0c;我们了解到很多用户自身的可视化项目&#xff0c;需要在垂直空间上表现一些业务&#xff0c;例如&#xff1a;3D地形效果&#xff0c;数据底板建设等&#xff0c;而传统的地图效果不满足此用户需求。瓦片地图能够无限加载大地图&#xff0c;以更三维的…

【Linux】在Ubuntu 16.04上安装Gerrit + PostgreSQL + Apache服务

Gerrit是一个基于Git版本控制系统的运行于Web浏览器上的Code Review工具&#xff0c;本文叙述如何在Ubuntu 16.04上安装Gerrit服务。&#xff08;当然安装Gerrit的方法有很多&#xff0c;本文只是其中之一&#xff09; 文章目录 前提安装PostgreSQL数据库并创建用户下载、配置和…

【飞舞的花瓣】飞舞的花瓣代码||樱花代码||表白代码(完整代码)

关注微信公众号「ClassmateJie」有完整代码以及更多惊喜等待你的发现。 简介/效果展示 这段代码是一个HTML页面&#xff0c;其中包含一个canvas元素和相关的JavaScript代码。这个页面创建了一个飘落花瓣的动画效果。 代码【获取完整代码关注微信公众号「ClassmateJie」回复“…

MySQL什么时候 锁表?如何防止锁表?

锁表会带来一系列问题&#xff0c;影响数据库的性能和系统的稳定性。 主要是下面的四个问题&#xff1a; 性能问题、死锁问题、可用性问题、一致性问题 1. 锁表带来的性能问题 锁表会阻止其他事务对该表的并发访问&#xff0c;包括读操作和写操作。 锁表会导致严重的性能问…

一步将 CentOS 7.X 原地升级并迁移至 RHEL 7.9

《OpenShift / RHEL / DevSecOps 汇总目录》 在《在离线环境中将 CentOS 7.X 原地升级并迁移至 RHEL 7.9》一文中为了实现从 CentOS 7.X 原地升级并迁移至 RHEL 7.9&#xff0c;我们第一步先将一个测试环境 CentOS 7.5 升级到 CentOS 7.9&#xff0c;然后在第二步使用 convert2…

Golang gin框架中间件c.JSON返回结果后终止返回

gin框架中间件c.JSON返回结果后还是会继续执行之后的方法&#xff0c;我们可以用c.Abort()来终止后续的处理 func MiddlewareFunction(c *gin.Context) {// 假设有某种条件下需要返回错误if someCondition {c.JSON(http.StatusBadRequest, gin.H{"error": "som…

Java8lambda和Java8Stream

匿名函数 为了简化Java中的匿名内部类 事件监听 写一个类 实现ActionListener 接口&#xff08;外部类&#xff09; 内部类 Lambda 表达式是一个匿名函数&#xff0c;我们可以把 lambda 表达式理解为一段 可以传递的代码&#xff08;将代码段像数据一样传递&#xff09;。使…

机器学习实验------softmax回归

第1关:softmax回归原理 任务描述 本关任务:使用Python实现softmax函数。 #encoding=utf8 import numpy as npdef softmax(x):input:x(ndarray):输入数据,shape=(m,n)output:y(ndarray):经过softmax函数后的输出shape=(m,n)#********* Begin *********#x -= np.max(x, axis …

C++的模板(七):左值强制类型转换

C中有个特殊指针类型&#xff0c;就是指向数据成员的指针。这个数据成员的指针是可以提取出来的。如: class Whatever { public:int x;int z; };int Whatever::*mp; mp &Whatever::x;如果数据的访问权限是private&#xff0c;则这样不可以。但可以通过模板的办法挑出来&…

抖音运营_如何开抖店

截止20年8月&#xff0c;抖音的日活跃数高达6亿。 20年6月&#xff0c;上线抖店 &#xff08;抖音官方电商&#xff09; 一 抖店的定位和特色 1 一站式经营 帮助商家进行 商品交易、店铺管理、客户服务 等全链路的生意经营 2 多渠道拓展 抖音、今日头条、西瓜、抖音火山版…

科兴未来|2024年诸暨市海内外高层次人才创业大赛

为深入实施人才强市、创新强市首位战略,加快人才链、产业链、创新链深度融合,努力开创“诸才云集”生动局面,奋力打造新时代共同富裕新高地,高水平谱写社会主义现代化建设诸暨篇章,推动诸暨实现高水平建设&#xff0c;构建全新人才创业创新生态体系&#xff0c;助力人才流入诸暨…