Golang:精通sync/atomic 包的Atomic 操作

在本指南中,我们将探索sync/atomic包的细节,展示如何编写更安全、更高效的并发代码。无论你是经验丰富的Gopher还是刚刚起步,你都会发现有价值的见解来提升Go编程技能。让我们一起开启原子运算的力量吧!

理解Go中的原子操作

在快节奏的并发编程世界中,原子操作是线程安全的哨兵。但是Go中的原子操作到底是什么,为什么要关心呢?让我们开始吧!
在这里插入图片描述

原子操作是不可分割的操作,对系统的其余部分来说似乎是瞬间发生的。在Go语言中,sync/atomic包提供这些操作,确保对共享变量的复杂操作不会中断。这在并发编程中是至关重要的,因为多个程序可能同时访问相同的数据。

考虑一下:你正在构建一个高流量的web应用程序,并且您需要跟踪活跃用户的数量。如果没有原子操作,您可能会遇到经典的竞争条件:

var activeUsers intfunc incrementUsers() {activeUsers++ // This is not atomic!
}

在并发环境中,这种看似无害的增量可能导致数据竞争和错误计数。下面示例采用原子操作:

import "sync/atomic"var activeUsers int64func incrementUsers() {atomic.AddInt64(&activeUsers, 1) // Atomic and safe!
}

现在,无论有多少例程调用incrementUsers(),计数总是准确的。

但是为什么要使用原子操作而不是互斥锁或其他同步方法呢?这一切都与性能有关。与基于锁的操作相比,原子操作快如闪电。事实上,Go团队的一项研究表明,对于简单的操作,原子可以比互斥体快3倍!

下面是一个简短的对比:

OperationAtomicMutex
Read2 ns6 ns
Write3 ns8 ns
CAS4 ns10 ns

(注:这些是近似值,可能会因硬件和Go版本而异)

原子操作适用于需要快速、简单同步的场景。它们适合:

  1. 计数器(像我们的活动用户示例)

  2. 标志(例如,检查进程是否完成)

  3. 简单共享状态管理

然而,需要注意的是,原子操作并不是万能的。对于复杂的数据结构或需要自动执行多个相关操作时,互斥锁或其他同步原语可能更合适。当我们深入研究同步/原子包时,请记住:能力越大责任越大。明智地使用原子操作,您的并发Go程序将以改进的性能和可靠性感谢您。

探索sync/atomic包

sync/atomic包是Go中并发编程工具的宝库。它就像一把瑞士军刀,可以安全有效地处理共享变量。让我们打开这个强大的包,看看它有什么提供!
在这里插入图片描述

sync/atomic的核心是提供低级原子内存原语,这些原语是同步算法的构建块。这些原语是用汇编语言实现的,以获得最大的效率,使它们非常快。

主要类型和功能

该包主要处理这些类型:

  • int32, int64
  • uint32, uint64
  • uintptr
  • unsafe.Pointer

对于每种类型,sync/atomic都提供了一组函数:

  1. Load:自动加载并返回变量的值。

  2. Store:自动地将值存储到变量中。

  3. Add:自动地向变量添加一个值并返回新值。

  4. Swap:自动地将一个值与一个变量交换,并返回旧值。

  5. CompareAndSwap:自动比较变量与旧值,如果它们相等,则将其与新值交换。

让我们看看这些操作:

import ("fmt""sync/atomic"
)func main() {var counter int64// Storeatomic.StoreInt64(&counter, 42)// Loadvalue := atomic.LoadInt64(&counter)fmt.Println("Counter value:", value)// AddnewValue := atomic.AddInt64(&counter, 10)fmt.Println("New counter value:", newValue)// SwapoldValue := atomic.SwapInt64(&counter, 100)fmt.Println("Old value:", oldValue, "New value:", atomic.LoadInt64(&counter))// CompareAndSwapswapped := atomic.CompareAndSwapInt64(&counter, 100, 200)fmt.Println("Swapped:", swapped, "Current value:", atomic.LoadInt64(&counter))
}

输出如下:

Counter value: 42
New counter value: 52
Old value: 52 New value: 100
Swapped: true Current value: 200

原子值操作

除了这些基本操作之外,sync/atomic还提供了Value类型,用于自动存储和加载任意值:

type Config struct {Threshold intName      string
}func main() {var configValue atomic.Value// Store a ConfigconfigValue.Store(Config{Threshold: 10, Name: "Default"})// Load the Configconfig := configValue.Load().(Config)fmt.Printf("Config: %+v\n", config)
}

这对于在并发环境中安全地更新配置值非常有用。

原子指针操作

对于更高级的用例,sync/atomic提供了指针上的原子操作:

type User struct {Name stringAge  int
}func main() {var userPtr atomic.Pointer[User]// Store a UseruserPtr.Store(&User{Name: "Alice", Age: 30})// Load the Useruser := userPtr.Load()fmt.Printf("User: %+v\n", *user)
}

这允许对复杂数据结构进行原子操作,从而启用无锁算法和数据结构。
在这里插入图片描述

性能考虑

虽然原子操作很快,但它们不是免费的。下面是一个比较原子操作和常规操作的快速基准测试:

func BenchmarkRegularIncrement(b *testing.B) {var x int64for i := 0; i < b.N; i++ {x++}
}func BenchmarkAtomicIncrement(b *testing.B) {var x int64for i := 0; i < b.N; i++ {atomic.AddInt64(&x, 1)}
}

运行这个基准测试可能会产生如下结果:

BenchmarkRegularIncrement-8    1000000000    0.3 ns/op
BenchmarkAtomicIncrement-8     100000000     12 ns/op

正如您所看到的,原子操作比常规操作慢。但是,在需要同步的并发场景中,它们通常比使用mutexes更快。

sync/atomic包是Go的并发工具包中的一个强大工具。通过理解它的功能并明智地使用它,您可以编写高效、无竞争的并发代码。

记住,能力越大责任越大。明智地使用原子操作,你的Go程序将会因为性能和可靠性的提高而感谢你

实现原子计数器

原子计数器是并发编程中的基本构建块,它允许多个例程安全地增加或减少共享值。让我们探索一下如何使用sync/atomic包来实现它们。

  • 创建和初始化原子计数器

在Go中,我们通常使用int64作为原子计数器。下面是如何创建和初始化一个:

import ("sync/atomic"
)var counter int64 // Initialized to 0 by default// Or, if you want to start with a non-zero value:
counter := atomic.Int64{}
counter.Store(100)
  • 递增和递减计数器

要自动修改计数器,可以使用AddInt64函数:

// Increment
atomic.AddInt64(&counter, 1)// Decrement
atomic.AddInt64(&counter, -1)// Add or subtract any value
atomic.AddInt64(&counter, 10)
atomic.AddInt64(&counter, -5)
  • 读计数器值

要读取计数器的当前值,使用LoadInt64:

currentValue := atomic.LoadInt64(&counter)
fmt.Printf("Current counter value: %d\n", currentValue)
  • 使用原子计数器的最佳实践
  1. 使用正确的类型:对于原子计数器始终使用int64,以确保所有体系结构上的64位对齐。

  2. 避免混合访问:不要在同一个变量上混合使用原子操作和非原子操作。

  3. 考虑溢出:记住原子计数器可以溢出,就像常规整数一样。

  4. 正确使用指针:始终向原子函数传递指针。

下面是演示这些实践的完整示例:

package mainimport ("fmt""sync""sync/atomic"
)func main() {var counter int64var wg sync.WaitGroupfor i := 0; i < 1000; i++ {wg.Add(1)go func() {atomic.AddInt64(&counter, 1)wg.Done()}()}wg.Wait()fmt.Printf("Final counter value: %d\n", atomic.LoadInt64(&counter))
}

这个程序创建了1000个例程,每个例程增加一次计数器。由于原子操作,最终值将始终为1000。

处理原子布尔值

原子布尔值对于在并发程序中实现标志非常有用。虽然Go没有专用的原子布尔类型,但我们可以使用uint32来原子地表示布尔值。

  • 设置和获取原子布尔值

下面是如何处理原子布尔值:

import ("sync/atomic"
)var flag uint32// Set the flag to true
atomic.StoreUint32(&flag, 1)// Set the flag to false
atomic.StoreUint32(&flag, 0)// Check if the flag is set
if atomic.LoadUint32(&flag) == 1 {fmt.Println("Flag is set!")
} else {fmt.Println("Flag is not set.")
}
  • 在并发程序中实现标志

原子布尔值非常适合实现控制多个线程行为的标志。下面是带有停止标志的简单worker池的例子:

package mainimport ("fmt""sync""sync/atomic""time"
)func main() {var stopFlag uint32var wg sync.WaitGroup// Start 5 workersfor i := 0; i < 5; i++ {wg.Add(1)go worker(i, &stopFlag, &wg)}// Let workers run for 2 secondstime.Sleep(2 * time.Second)// Signal workers to stopatomic.StoreUint32(&stopFlag, 1)wg.Wait()fmt.Println("All workers stopped")
}func worker(id int, stopFlag *uint32, wg *sync.WaitGroup) {defer wg.Done()for {if atomic.LoadUint32(stopFlag) == 1 {fmt.Printf("Worker %d stopping\n", id)return}// Simulate some worktime.Sleep(200 * time.Millisecond)fmt.Printf("Worker %d working\n", id)}
}
  • 原子布尔变量与常规布尔变量

对布尔变量使用原子操作比常规布尔变量有一些优势:

  1. 线程安全:原子布尔值在并发环境中使用是安全的,不需要额外的同步。

  2. 性能优势:对于简单的标志,原子布尔值通常比使用互斥锁更快。

  3. 可见性保证:原子操作确保所有例程都能立即看到更改。

然而,也有取舍:

  1. 内存使用:原子布尔值使用32位而不是常规布尔值的1位。

  2. 复杂性:语法稍微复杂一些

原子交换和比较交换(CAS)

原子交换和比较与交换(CAS)操作是高级原子原语,构成了许多无锁算法的主干。这些操作允许进行比简单的加载和存储更复杂的原子更新,从而实现各种并发数据结构和算法的高效和线程安全的实现。

  • 实现原子交换操作

Swap操作自动地将变量的值与新值交换,并返回旧值。这在需要更新值的同时还需要知道其先前状态的情况下非常有用。

下面是如何使用Swap操作:

package mainimport ("fmt""sync/atomic"
)func main() {var value int64 = 100// Atomically swap the value and get the old valueoldValue := atomic.SwapInt64(&value, 200)fmt.Printf("Old value: %d, New value: %d\n", oldValue, atomic.LoadInt64(&value))
}

输出:

Old value: 100, New value: 200

交换操作可用于各种类型:

  • SwapInt32, SwapInt64
  • SwapUint32, SwapUint64
  • SwapUintptr
  • SwapPointer

理解比较与交换(CAS)

比较与交换(CAS)是一种更强大的原语,它支持仅在值与预期值匹配时更新值。这个操作是许多无锁算法的基础。

以下是CAS的基本思想:

  1. 读取变量的当前值。

  2. 基于该值执行计算。

  3. 更新变量,但前提是它仍然具有您最初读取的值。

如果该值在步骤1和步骤3之间发生了更改,则CAS操作失败,通常需要重试该操作。

这里有一个简单的例子:

package mainimport ("fmt""sync/atomic"
)func main() {var value int64 = 100// Try to update value from 100 to 200swapped := atomic.CompareAndSwapInt64(&value, 100, 200)fmt.Printf("CAS successful: %v, New value: %d\n", swapped, atomic.LoadInt64(&value))// Try again, but it will fail because value is no longer 100swapped = atomic.CompareAndSwapInt64(&value, 100, 300)fmt.Printf("CAS successful: %v, New value: %d\n", swapped, atomic.LoadInt64(&value))
}

输出结果:

CAS successful: true, New value: 200
CAS successful: false, New value: 200

总结

我们已经游历了Go的sync/atomic包的迷人世界,揭示了原子操作的强大和巧妙。从掌握原子计数器到使用Compare-and-Swap实现无锁算法,您现在可以编写更高效、更安全的并发围棋程序了。请记住,虽然原子操作可以使代码负担过重,但它们需要仔细考虑并正确实现。

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

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

相关文章

Mp4视频播放机无法播放视频-批量修改视频分辨率(帧宽、帧高)

背景 家人有一台夏新多功能 视频播放器(夏新多功能 视频播放器),用来播放广场舞。下载了一些广场舞视频, 只有部分视频可以播放,其他视频均无法播放,判断应该不是帧速率和数据速率的限制, 分析可能是播放器不支持帧高度大于720的视频。由于视频文件较多,需要借助视频编…

【Python】字典

个人主页&#xff1a;GUIQU. 归属专栏&#xff1a;Python 文章目录 1. 字典概述2. 字典的创建与初始化2.1 直接使用花括号创建2.2 使用 dict() 构造函数创建2.3 字典推导式创建 3. 字典的基本操作3.1 访问字典中的值3.2 修改和添加键值对3.3 删除键值对 4. 字典的遍历4.1 遍历键…

STM32系统架构介绍

STM32系统架构 1. CM3/4系统架构2. CM3/4系统架构-----存储器组织结构2.1 寄存器地址映射&#xff08;特殊的存储器&#xff09;2.2 寄存器地址计算2.3 寄存器的封装 3. CM3/4系统架构-----时钟系统 STM32 和 ARM 以及 ARM7是什么关系? ARM 是一个做芯片标准的公司&#xff0c…

鸿蒙NEXT开发-发布三方库

开发一个三方库 如需发布一个 har 包&#xff0c;必须包含 oh-package.json5、README.md&#xff0c;CHANGELOG.md 和 LICENSE 四个文件&#xff0c;若文件缺失&#xff0c;会导致上架至中心仓失败。 HAR&#xff08;Harmony Archive&#xff09;是静态共享包&#xff0c;可以…

CSS 实现下拉菜单效果实例解析

1. 引言 在 Web 开发过程中&#xff0c;下拉菜单是一种常见且十分实用的交互组件。很多前端教程都提供过简单的下拉菜单示例&#xff0c;本文将以一个简洁的实例为出发点&#xff0c;从 HTML 结构、CSS 样式以及整体交互逻辑三个层面进行详细解析&#xff0c;帮助大家理解纯 C…

半导体制造工艺讲解

目录 一、半导体制造工艺的概述 二、单晶硅片的制造 1.单晶硅的制造 2.晶棒的切割、研磨 3.晶棒的切片、倒角和打磨 4.晶圆的检测和清洗 三、晶圆制造 1.氧化与涂胶 2.光刻与显影 3.刻蚀与脱胶 4.掺杂与退火 5.薄膜沉积、金属化和晶圆减薄 6.MOSFET在晶圆表面的形…

微信小程序如何使用decimal计算金额

第三方库地址&#xff1a;GitHub - MikeMcl/decimal.js: An arbitrary-precision Decimal type for JavaScript 之前都是api接口走后端计算&#xff0c;偶尔发现这个库也不错&#xff0c;计算简单&#xff0c;目前发现比较准确 上代码 导入js import Decimal from ../../uti…

安卓开发,底部导航栏

1、创建导航栏图标 使用系统自带的矢量图库文件&#xff0c;鼠标右键点击res->New->Vector Asset 修改 Name , Clip art 和 Color 再创建一个 同样的方法再创建四个按钮 2、添加百分比布局依赖 app\build.gradle.kts 中添加百分比布局依赖&#xff0c;并点击Sync Now …

前后端服务配置

1、安装虚拟机&#xff08;VirtualBox或者vmware&#xff09;&#xff0c;在虚拟机上配置centos(选择你需要的Linux版本)&#xff0c;配置如nginx服务器等 1.1 VMware 下载路径Sign In注册下载 1.2 VirtualBox 下载路径https://www.virtualbox.org/wiki/Downloads 2、配置服…

亲身经历!!解决fatal: unable to access ‘https://~.git/‘: Failed to connect to github.com ····

最近学着用GitBash,发现上来gitclone 就报错。由于我是纯小白&#xff0c;所以试了比较多次&#xff0c;终于成功了。 首先我试了一下关闭梯子&#xff0c;发现还是不行。上网搜&#xff0c;说是代理问题&#xff0c;可我也不知道啥叫代理&#xff0c;反正大概意思就是电脑连通…

TCP传输层协议

TCP 全称为 "传输控制协议(Transmission Control Protocol"). 人如其名, 要对数据的传 输进行一个详细的控制。 对于TCP的学习主要就是要知道TCP协议报头之中各个字段的作用 除了数据之外总共报头加起来是20个字节 16位源端口号与目的端口号 这是最容易理解的两…

正则引入store中的modules文件

正则引入store中的modules文件 // index.js import { createStore } from vuex;const modulesFiles require.context(./modules, true, /\.ts|js$/); const modules modulesFiles.keys().reduce((modules1, modulePath) > {const moduleName modulePath.replace(/^\.\/(.…

127,【3】 buuctf [NPUCTF2020]ReadlezPHP

进入靶场 吓我一跳 查看源码 点击 审计 <?php// 定义一个名为 HelloPhp 的类&#xff0c;该类可能用于执行与日期格式化相关的操作 class HelloPhp {// 定义一个公共属性 $a&#xff0c;用于存储日期格式化的模板public $a;// 定义一个公共属性 $b&#xff0c;用于存储…

如何在浏览器中搭建开源Web操作系统Puter的本地与远程环境

文章目录 前言1.关于Puter2.本地部署Puter3.Puter简单使用4. 安装内网穿透5.配置puter公网地址6. 配置固定公网地址 前言 嘿&#xff0c;小伙伴们&#xff01;是不是每次开机都要像打地鼠一样不停地点击各种网盘和应用程序的登录按钮&#xff0c;感觉超级麻烦&#xff1f;更让…

产品详情页中 品牌官网详情 对应后端的字段是 detail

文章目录 1、在这个Vue代码中&#xff0c;品牌官网详情 对应后端的字段是 detail2、品牌官网详情 功能相关的代码片段3、export const productSave (data: any) >4、ProductController5、ProductDto 类6、ProductApiService 1、在这个Vue代码中&#xff0c;品牌官网详情 对…

51单片机(国信长天)矩阵键盘的基本操作

在CT107D单片机综合训练平台上&#xff0c;首先将J5处的跳帽接到1~2引脚&#xff0c;使按键S4~S19按键组成4X4的矩阵键盘。在扫描按键的过程中&#xff0c;发现有按键触发信号后(不做去抖动)&#xff0c;待按键松开后&#xff0c;在数码管的第一位显示相应的数字:从左至右&…

VSCode + Continue 实现AI编程助理

安装VS Code 直接官网下载安装&#xff0c;反正是免费的。 安装VS插件Continue 直接在插件市场中搜索&#xff0c; Continue&#xff0c;第一个就是了。 配置Chat Model 点击Add Chat model后进行选择&#xff1a; 选择Ollama后&#xff0c;需要点击下面的config file : 由于…

机器学习之数学基础:线性代数、微积分、概率论 | PyTorch 深度学习实战

前一篇文章&#xff0c;使用线性回归模型逼近目标模型 | PyTorch 深度学习实战 本系列文章 GitHub Repo: https://github.com/hailiang-wang/pytorch-get-started 本篇文章内容来自于 强化学习必修课&#xff1a;引领人工智能新时代【梗直哥瞿炜】 线性代数、微积分、概率论 …

BS架构(笔记整理)

楔子.基本概念 1.在网络架构中&#xff1a; 服务器通常是集中式计算资源&#xff0c;负责处理和存储数据&#xff1b;客户机是请求这些服务的终端设备&#xff0c;可能是个人电脑或移动设备&#xff1b;浏览器则是客户机上用来与服务器交互的工具&#xff0c;负责展示网页内容…

云消息队列 ApsaraMQ Serverless 演进:高弹性低成本、更稳定更安全、智能化免运维

如今&#xff0c;消息队列已成为分布式架构中不可或缺的关键服务&#xff0c;为电商、物联网、游戏和教育等行业&#xff0c;提供了异步解耦、集成、高性能和高可靠的核心价值。 过去一年&#xff0c;我们发布了云消息队列 ApsaraMQ 全系列产品 Serverless 化&#xff0c;面向…