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

news2025/2/11 3:48:02

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

理解Go中的原子操作

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

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

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

var activeUsers int

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

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

import "sync/atomic"

var activeUsers int64

func 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

    // Store
    atomic.StoreInt64(&counter, 42)

    // Load
    value := atomic.LoadInt64(&counter)
    fmt.Println("Counter value:", value)

    // Add
    newValue := atomic.AddInt64(&counter, 10)
    fmt.Println("New counter value:", newValue)

    // Swap
    oldValue := atomic.SwapInt64(&counter, 100)
    fmt.Println("Old value:", oldValue, "New value:", atomic.LoadInt64(&counter))

    // CompareAndSwap
    swapped := 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 int
    Name      string
}

func main() {
    var configValue atomic.Value

    // Store a Config
    configValue.Store(Config{Threshold: 10, Name: "Default"})

    // Load the Config
    config := configValue.Load().(Config)
    fmt.Printf("Config: %+v\n", config)
}

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

原子指针操作

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

type User struct {
    Name string
    Age  int
}

func main() {
    var userPtr atomic.Pointer[User]

    // Store a User
    userPtr.Store(&User{Name: "Alice", Age: 30})

    // Load the User
    user := userPtr.Load()
    fmt.Printf("User: %+v\n", *user)
}

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

性能考虑

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

func BenchmarkRegularIncrement(b *testing.B) {
    var x int64
    for i := 0; i < b.N; i++ {
        x++
    }
}

func BenchmarkAtomicIncrement(b *testing.B) {
    var x int64
    for 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 main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    var counter int64
    var wg sync.WaitGroup

    for 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 main

import (
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)

func main() {
    var stopFlag uint32
    var wg sync.WaitGroup

    // Start 5 workers
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go worker(i, &stopFlag, &wg)
    }

    // Let workers run for 2 seconds
    time.Sleep(2 * time.Second)

    // Signal workers to stop
    atomic.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 work
        time.Sleep(200 * time.Millisecond)
        fmt.Printf("Worker %d working\n", id)
    }
}
  • 原子布尔变量与常规布尔变量

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

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

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

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

然而,也有取舍:

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

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

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

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

  • 实现原子交换操作

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

下面是如何使用Swap操作:

package main

import (
    "fmt"
    "sync/atomic"
)

func main() {
    var value int64 = 100

    // Atomically swap the value and get the old value
    oldValue := 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 main

import (
    "fmt"
    "sync/atomic"
)

func main() {
    var value int64 = 100

    // Try to update value from 100 to 200
    swapped := 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 100
    swapped = 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.coloradmin.cn/o/2296108.html

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

相关文章

代码随想录_二叉树

二叉树 二叉树的递归遍历 144.二叉树的前序遍历145.二叉树的后序遍历94.二叉树的中序遍历 // 前序遍历递归LC144_二叉树的前序遍历 class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> result new ArrayList<Integer&g…

【C语言标准库函数】浮点数分解与构造: frexp() 和 ldexp()

目录 一、头文件 二、函数简介 2.1. frexp(double x, int *exp) 2.2. ldexp(double x, int exp) 三、函数实现&#xff08;概念性&#xff09; 3.1. frexp 的概念性实现 3.2. ldexp 的概念性实现 四、注意事项 五、示例代码 在C语言标准库中&#xff0c;frexp() 和 ld…

【Git】tortoisegit使用配置

1. 安装 首先下载小乌龟&#xff0c;下载地址:https://tortoisegit.org/download/, 可以顺便下载语言包&#xff01; 安装时&#xff0c;默认安装就可以&#xff0c;一路next。也可以安装到指定目录中 目前已完成本地安装&#xff0c;接下来就需要与远程仓库建立连接&…

Spring基于文心一言API使用的大模型

有时做项目我们可能会遇到要在项目中对接AI大模型 本篇文章是对使用文心一言大模型的使用总结 前置任务 在百度智能云开放平台中注册成为开发者 百度智能云开放平台 进入百度智能云官网进行登录&#xff0c;点击立即体验 点击千帆大模型平台 向下滑动&#xff0c;进入到模型…

Centos Ollama + Deepseek-r1+Chatbox运行环境搭建

Centos Ollama Deepseek-r1Chatbox运行环境搭建 内容介绍下载ollama在Ollama运行DeepSeek-r1模型使用chatbox连接ollama api 内容介绍 你好&#xff01; 这篇文章简单讲述一下如何在linux环境搭建 Ollama Deepseek-r1。并在本地安装的Chatbox中进行远程调用 下载ollama 登…

SpringSecurity:授权服务器与客户端应用(入门案例)

文章目录 一、需求概述二、开发授权服务器1、pom依赖2、yml配置3、启动服务端 三、开发客户端应用1、pom依赖2、yml配置3、SecurityConfig4、接口5、测试 一、需求概述 maven需要3.6.0以上版本 二、开发授权服务器 1、pom依赖 <dependency><groupId>org.springfr…

Python与java的区别

一开始接触Python的时候&#xff0c;哔哩视频铺天盖地&#xff0c;看了很多人主讲的&#xff0c;要找适合自己口味的&#xff0c;各种培训机构喜欢在各种平台引流打广告&#xff0c;看了很多家&#xff0c;要么就是一个视频几个小时&#xff0c;长篇大论不讲原理只讲应用&#…

大数据学习之SparkSql

95.SPARKSQL_简介 网址&#xff1a; https://spark.apache.org/sql/ Spark SQL 是 Spark 的一个模块&#xff0c;用于处理 结构化的数据 。 SparkSQL 特点 1 易整合 无缝的整合了 SQL 查询和 Spark 编程&#xff0c;随时用 SQL 或 DataFrame API 处理结构化数据。并且支…

鸿蒙UI(ArkUI-方舟UI框架)- 使用文本

返回主章节 → 鸿蒙UI&#xff08;ArkUI-方舟UI框架&#xff09; 文本使用 文本显示 (Text/Span) Text是文本组件&#xff0c;通常用于展示用户视图&#xff0c;如显示文章的文字内容。Span则用于呈现显示行内文本。 创建文本 string字符串 Text("我是一段文本"…

Spider 数据集上实现nlp2sql训练任务

NLP2SQL&#xff08;自然语言处理到 SQL 查询的转换&#xff09;是一个重要的自然语言处理&#xff08;NLP&#xff09;任务&#xff0c;其目标是将用户的自然语言问题转换为相应的 SQL 查询。这一任务在许多场景下具有广泛的应用&#xff0c;尤其是在与数据库交互的场景中&…

【DeepSeek】DeepSeek概述 | 本地部署deepseek

目录 1 -> 概述 1.1 -> 技术特点 1.2 -> 模型发布 1.3 -> 应用领域 1.4 -> 优势与影响 2 -> 本地部署 2.1 -> 安装ollama 2.2 -> 部署deepseek-r1模型 1 -> 概述 DeepSeek是由中国的深度求索公司开发的一系列人工智能模型&#xff0c;以其…

ASP.NET Core 使用 WebClient 从 URL 下载

本文使用 ASP .NET Core 3.1&#xff0c;但它在.NET 5、 .NET 6和.NET 8上也同样适用。如果使用较旧的.NET Framework&#xff0c;请参阅本文&#xff0c;不过&#xff0c;变化不大。 如果想要从 URL 下载任何数据类型&#xff0c;请参阅本文&#xff1a;HttpClient 使用WebC…

【CubeMX-HAL库】STM32F407—无刷电机学习笔记

目录 简介&#xff1a; 学习资料&#xff1a; 跳转目录&#xff1a; 一、工程创建 二、板载LED 三、用户按键 四、蜂鸣器 1.完整IO控制代码 五、TFT彩屏驱动 六、ADC多通道 1.通道确认 2.CubeMX配置 ①开启对应的ADC通道 ②选择规则组通道 ③开启DMA ④开启ADC…

vue3 点击图标从相册选择二维码图片,并使用jsqr解析二维码(含crypto-js加密解密过程)

vue3 点击图标从相册选择二维码图片&#xff0c;并使用jsqr解析二维码&#xff08;含crypto-js加密解密过程&#xff09; 1.安装 jsqr 和 crypto-js npm install -d jsqr npm install crypto-js2.在util目录下新建encryptionHelper.js文件&#xff0c;写加密解密方法。 // e…

kafka 3.5.0 raft协议安装

前言 最近做项目&#xff0c;需要使用kafka进行通信&#xff0c;且只能使用kafka&#xff0c;笔者没有测试集群&#xff0c;就自己搭建了kafka集群&#xff0c;实际上笔者在很早之前就搭建了&#xff0c;因为当时还是zookeeper&#xff08;简称ZK&#xff09;注册元数据&#…

前后端服务配置

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、配置服…

在阿里云ECS上一键部署DeepSeek-R1

DeepSeek-R1 是一款开源模型&#xff0c;也提供了 API(接口)调用方式。据 DeepSeek介绍&#xff0c;DeepSeek-R1 后训练阶段大规模使用了强化学习技术&#xff0c;在只有极少标注数据的情况下提升了模型推理能力&#xff0c;该模型性能对标 OpenAl o1 正式版。DeepSeek-R1 推出…

git SourceTree 使用

Source Tree 使用原理 文件的状态 创建仓库和提交 验证 再克隆的时候发发现一个问题&#xff0c;就是有一个 这个验证&#xff0c;起始很简单 就是 gitee 的账号和密码&#xff0c;但是要搞清楚的是账号不是名称&#xff0c;我之前一直再使用名称登录老是出问题 这个很简单的…

游戏引擎学习第94天

仓库:https://gitee.com/mrxiao_com/2d_game_2 回顾上周的渲染器工作 完成一款游戏的开发&#xff0c;完全不依赖任何库和引擎&#xff0c;这样我们能够全面掌握游戏的开发过程&#xff0c;确保没有任何细节被隐藏。我们将深入探索每一个环节&#xff0c;犹如拿着手电筒翻看床…

win32汇编环境,结构体的使用示例二

;运行效果 ;win32汇编环境,结构体的使用示例二 ;举例说明结构体的定义&#xff0c;如何访问其中的成员&#xff0c;使用assume指令指向某个结构体&#xff0c;计算结构数组所需的偏移量得到某个成员值等 ;直接抄进RadAsm可编译运行。重要部分加备注。 ;下面为asm文件 ;>>…