【Go】原子并发操作

        

目录

一、基本概念

支持的数据类型

主要函数

使用场景

二、基础代码实例

开协程给原子变量做加法

统计多个变量

原子标志判断

三、并发日志记录器

四、并发计数器与性能监控

五、优雅的停止并发任务

worker函数

Main函数

应用价值


        Go语言中,原子并发操作是非常常用的,确保协程环境中对资源的共访是安全的。Go的sync/atomic包提供了一系列底层的原子性操作函数,允许你在基本数据类型上执行无锁的线程安全操作。使用原子操作可以避免在并发访问时使用互斥锁(mutexes),从而在某些情况下提高性能。

一、基本概念

        原子操作可以保证在多线程环境中,单个操作是不可中断的,即在完成之前不会被线程切换影响。这是通过硬件级别的支持实现的,确保了操作的原子性。

支持的数据类型

Go的sync/atomic包支持几种基本数据类型的原子操作:

  • int32, int64
  • uint32, uint64
  • uintptr
  • Pointer(对于任意类型的原子指针操作)

主要函数

  • AddInt32, AddInt64, AddUint32, AddUint64: 原子加法操作。
  • LoadInt32, LoadInt64, LoadUint32, LoadUint64: 原子加载操作,用于读取一个值。
  • StoreInt32, StoreInt64, StoreUint32, StoreUint64: 原子存储操作,用于写入一个值。
  • SwapInt32, SwapInt64, SwapUint32, SwapUint64: 原子交换操作,写入新值并返回旧值。
  • CompareAndSwapInt32, CompareAndSwapInt64, CompareAndSwapUint32, CompareAndSwapUint64: 比较并交换操作,如果当前值等于旧值,则写入新值。

使用场景

原子操作通常用在性能敏感且要求高并发的场景,例如:

  • 实时计数器
  • 状态标志
  • 无锁数据结构的实现

        原子操作提供了一种比锁更轻量级的并发控制方法,尤其适用于操作简单且频繁的场景。不过,原子操作的使用需要更谨慎,以避免复杂逻辑中可能的逻辑错误。在设计并发控制策略时,适当的选择使用锁还是原子操作,可以帮助你更好地平衡性能和开发效率。

二、基础代码实例

开协程给原子变量做加法

package mainimport ("fmt""sync""sync/atomic"
)func main() {// We'll use an atomic integer type to represent our// (always-positive) counter.var ops atomic.Uint64// A WaitGroup will help us wait for all goroutines// to finish their work.var wg sync.WaitGroup// We'll start 50 goroutines that each increment the// counter exactly 1000 times.for i := 0; i < 500; i++ {wg.Add(1)go func() {for c := 0; c < 10000; c++ {// To atomically increment the counter we use `Add`.ops.Add(1)}wg.Done()}()}// Wait until all the goroutines are done.wg.Wait()// Here no goroutines are writing to 'ops', but using// `Load` it's safe to atomically read a value even while// other goroutines are (atomically) updating it.fmt.Println("ops:", ops.Load())
}

统计多个变量

        我们可以使用多个原子变量来跟踪不同类型的操作。

package mainimport ("fmt""sync""sync/atomic"
)func main() {var adds atomic.Uint64var subs atomic.Uint64var wg sync.WaitGroupfor i := 0; i < 50; i++ {wg.Add(1)go func() {for c := 0; c < 1000; c++ {adds.Add(1)}wg.Done()}()wg.Add(1)go func() {for c := 0; c < 1000; c++ {subs.Add(1)}wg.Done()}()}wg.Wait()fmt.Println("Adds:", adds.Load(), "Subs:", subs.Load())
}

原子标志判断

        使用原子变量作为一个简单的标志来控制是否所有协程都应该停止工作。

package mainimport ("fmt""sync""sync/atomic""time"
)func main() {var ops atomic.Uint64var stopFlag atomic.Boolvar wg sync.WaitGroupfor i := 0; i < 50; i++ {wg.Add(1)go func() {for !stopFlag.Load() {ops.Add(1)time.Sleep(10 * time.Millisecond) // 减缓增加速度}wg.Done()}()}time.Sleep(500 * time.Millisecond) // 运行一段时间后stopFlag.Store(true)               // 设置停止标志wg.Wait()fmt.Println("ops:", ops.Load())
}

三、并发日志记录器

        在一个多协程环境中,我们可能需要记录日志,但又希望避免因为并发写入而导致的问题。我们可以使用sync.Mutex来确保日志写入的原子性。

package mainimport ("fmt""log""os""sync"
)var (logger   *log.LoggerlogMutex sync.Mutex
)func main() {file, err := os.OpenFile("log.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)if err != nil {log.Fatalln("Failed to open log file")}defer file.Close()logger = log.New(file, "", log.Ldate|log.Ltime|log.Lshortfile)var wg sync.WaitGroupfor i := 0; i < 1000; i++ {wg.Add(1)go func(id int) {logMutex.Lock()logger.Println("Goroutine %d is runing...", id)logMutex.Unlock()wg.Done()}(i)}wg.Wait()fmt.Println("All goroutines finished")
}

四、并发计数器与性能监控

        在一个网络服务器或数据库中,我们可能需要监控并发请求的数量或特定资源的使用情况,使用原子操作可以无锁地实现这一点。

package mainimport ("fmt""net/http""sync/atomic"
)var requestCount atomic.Int64func handler(w http.ResponseWriter, r *http.Request) {requestCount.Add(1)fmt.Fprintf(w, "Hello, visitor number %d!", requestCount.Load())
}func main() {http.HandleFunc("/", handler)http.ListenAndServe(":8080", nil)
}

 

 

五、优雅的停止并发任务

       在处理诸如网络服务或后台任务处理器的程序时,我们可能需要在收到停止信号后优雅地中断并发任务。

package mainimport ("context""fmt""sync""time"
)func worker(ctx context.Context, id int, wg *sync.WaitGroup) {defer wg.Done()for {select {case <-ctx.Done():fmt.Printf("Worker %d stopping\n", id)returndefault:fmt.Printf("Worker %d is working\n", id)time.Sleep(time.Second)}}
}func main() {var wg sync.WaitGroupctx, cancel := context.WithCancel(context.Background())for i := 1; i <= 5; i++ {wg.Add(1)go worker(ctx, i, &wg)}time.Sleep(5 * time.Second)cancel() // 发送取消信号wg.Wait()fmt.Println("All workers stopped.")
}

worker函数

func worker(ctx context.Context, id int, wg *sync.WaitGroup) {defer wg.Done()for {select {case <-ctx.Done():fmt.Printf("Worker %d stopping\n", id)returndefault:fmt.Printf("Worker %d is working\n", id)time.Sleep(time.Second)}}
}
  • worker是一个协程函数,接受一个context.Context对象、一个整数id作为工人标识,和一个sync.WaitGroup来同步协程。
  • defer wg.Done(): 确保在函数返回时调用wg.Done(),表明该协程的工作已完成,这对于等待组来维护协程计数非常重要。
  • select语句用于处理多个通道操作。在这里,它监听ctx.Done()通道,这是context提供的方式,用于接收上下文取消事件。
    • ctx.Done()通道接收到信号时(这发生在主协程调用cancel()函数时),输出停止信息,并通过return退出无限循环,结束协程执行。
    • default分支在没有信号时执行,模拟工作负载并通过time.Sleep(time.Second)模拟一秒钟的工作时间。

Main函数

  • ctx, cancel := context.WithCancel(context.Background()): 创建一个可取消的上下文ctx,和一个cancel函数,用于发送取消信号。
  • 循环启动5个工作协程,每个通过go worker(ctx, i, &wg)启动,并传递上下文、ID和等待组。
  • time.Sleep(5 * time.Second): 主协程等待5秒钟,给工作协程一定时间执行。
  • cancel(): 调用取消函数,这会向ctx.Done()发送信号,导致所有监听该通道的工作协程接收到取消事件并停止执行。
  • wg.Wait(): 阻塞直到所有工作协程调用wg.Done(),表明它们已经停止。
  • 输出“All workers stopped.”表示所有工作协程已经优雅地停止。

应用价值

        这种模式的使用在需要对多个并发执行的任务进行优雅中断和资源管理时非常有用,例如:

  • 处理HTTP请求时,在请求超时或取消时停止后台处理。
  • 控制长时间运行或复杂的后台任务,允许随时取消以释放资源。
  • 在微服务架构中,通过控制信号来优雅地关闭服务或组件。

        这种模式提高了程序的健壮性和响应性,使得程序能够在控制下安全地处理并发操作,同时减少资源的浪费。

总结

        Go语言中的原子操作是通过sync/atomic包提供的,用于实现低级的同步原语。原子操作可以在多协程环境中安全地操作数据,而不需要加锁,因此在某些场景下比使用互斥锁(mutex)具有更好的性能。下面是关于Go中原子操作及其相关内容的详细总结:

1. 原子操作的基本概念

        原子操作指的是在多线程或多协程环境中,能够保证中间状态不被其他线程观察到的操作。这些操作在执行的过程中不会被线程调度器中断,Go语言通过硬件支持保证了这些操作的原子性。

2. 原子类型支持

sync/atomic包支持以下基本数据类型的原子操作:

  • 整型(int32, int64
  • 无符号整型(uint32, uint64
  • 指针类型(uintptr
  • 更通用的指针操作(unsafe.Pointer

3. 主要原子操作

  • 加载(Load): 原子地读取变量的值。
  • 存储(Store): 原子地写入新值到变量。
  • 增加(Add): 原子地增加变量的值。
  • 交换(Swap): 原子地将变量设置为新值,并返回旧值。
  • 比较并交换(Compare And Swap, CAS): 如果当前变量的值等于旧值,则将变量设置为新值,并返回操作是否成功。

4. 原子操作的用途

        原子操作通常用于实现无锁的数据结构和算法,以及在不适合使用互斥锁的高并发场景中保护共享资源。它们特别适用于管理共享状态、实现简单的计数器或标志、以及在状态机中进行状态转换。

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

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

相关文章

Qt C++ 实现无边框窗口

Qt C 实现无边框窗口 // widget.h #ifndef WIDGET_H #define WIDGET_H#include <QDebug> #include <QHBoxLayout> #include <QMouseEvent> #include <QPushButton> #include <QString> #include <QWidget>#define PADDING 6enum Location…

SoC的启动流程 和MCU的启动流程 有什么区别?

SoC&#xff08;System on Chip&#xff09;和MCU&#xff08;Microcontroller Unit&#xff09;的启动流程在很多方面是相似的&#xff0c;因为它们都涉及到硬件的初始化和软件的加载。然而&#xff0c;由于SoC通常包含更复杂的系统集成和可能运行更高级的操作系统&#xff0c…

HLS视频播放在iOS和安卓平台的适配问题及解决方案

HLS视频播放在iOS和安卓平台的适配问题及解决方案 在移动端视频播放中&#xff0c;HLS&#xff08;HTTP Live Streaming&#xff09;是一种常用的流媒体传输协议&#xff0c;可以实现视频的分段传输和自适应码率调整&#xff0c;以提供更好的播放体验。然而&#xff0c;由于iO…

获取字符串的全排列(去除字符串中2个字符相同时造成的重复)

一、概念 现有一个字符串&#xff0c;要打印出该字符串中字符的全排列。 以字符串abc为例&#xff0c;输出的结果为&#xff1a;abc、acb、bac、bca、cab、cba。 以字符串aab为例&#xff0c;输出的结果为&#xff1a;aab、aba、baa。 二、代码 public class Permutation {pub…

Rabbitmq中的延迟队列是什么?有什么作用?如何使用?

1、Rabbitmq中的延迟队列是什么&#xff1f; 在RabbitMQ中&#xff0c;延迟队列是一个特殊的队列&#xff0c;用于存放需要在指定时间后被处理的消息。这种队列的主要特性是它可以为队列中的每个消息设置一定的延迟时间&#xff0c;只有在延迟时间到达后&#xff0c;消息才会被…

【Linux】Linux基础与常用指令大全

文章目录 操作系统是什么&#xff1f;1. Linux家族介绍2. Linux的安装方式3. 常用指令3.1 ls [选项] [目录/文件]&#xff08;显示目录或文件信息&#xff09;3.2 pwd&#xff08;显示当前所在目录&#xff09;3.3 任意指令加上 --help&#xff08;查看指令的用法&#xff09;3…

ThinkPHP V5.1框架源码

源码下载地址&#xff1a;ThinkPHP V5.1.zip www WEB部署目录&#xff08;或者子目录&#xff09; ├─application 应用目录 │ ├─common 公共模块目录&#xff08;可以更改&#xff09; │ ├─module_name 模块目录 │ │ ├─common.php 模块函数文件 │ │ ├─controll…

一文掌握 React 开发中的 JavaScript 基础知识

前端开发中JavaScript是基石。在 React 开发中掌握掌握基础的 JavaScript 方法将有助于编写出更加高效、可维护的 React 应用程序。 在 React 开发中使用 ES6 语法可以带来更简洁、可读性更强、功能更丰富,以及更好性能和社区支持等诸多好处。这有助于提高开发效率,并构建出更…

线性表概念及顺序表的实现

文章目录 前言一、线性表1.定义2.特点3.一般线性表的抽象数据类型定义 二、线性表的顺序存储&#xff08;顺序表&#xff09;1.基本概念2.数组实现顺序表3.顺序表中基本操作的具体实现4.顺序表总结 总结 前言 T_T此专栏用于记录数据结构及算法的&#xff08;痛苦&#xff09;学…

MyBatis 源码分析系列文章导读

1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章。本篇文章从 MyBatis 是什么&#xff08;what&#xff09;&#xff0c;为什么要使用&#xff08;why&#xff09;&#xff0c;以及如何使用&#xff08;how&#xff09;等三个角度进行了说明和演…

vue--数据代理与数据劫持

0.回顾Object.defineProperty()方法 let number 18let person {name:张三,sex:男,}Object.defineProperty(person,age,{// value:18,// enumerable:true, //控制属性是否可以枚举&#xff0c;默认值是false// writable:true, //控制属性是否可以被修改&#xff0c;默认值是fa…

通义灵码×西安交通大学携手打造“云工开物-高校训练营”,解锁 AI 时代编程学习与实战

作为大学生如何利用 AI “整活儿”&#xff1f;欢迎各位同学关注阿里云与西安交通大学计算机学院携手打造的“云工开物-高校训练营”&#xff0c;带你走近 AI 编程助手“通义灵码”。通义灵码是阿里推出的免费 AI 编程工具&#xff0c;拥有实时代码续写与优化、自然语言生成代码…

记一次对某高校微信小程序的漏洞挖掘

挖掘目标的部署在微信的资产(减少信息的收集&#xff0c;毕竟一般web站点没有账号密码不好进入后台&#xff0c;挖掘功能点少) 寻找目标的微信小程序(非原图) 招生小程序打不开&#xff0c;只能挖掘管理系统 进入后发现存在上报安全隐患功能&#xff0c;可以上传图片 准备上传…

浅谈Java21的新特性-虚拟线程

虚拟线程&#xff08;Virtual Threads&#xff09;&#xff0c;又称为用户模式线程&#xff08;User-Mode Threads&#xff09;或纤程&#xff08;Fibers&#xff09;&#xff0c;是一种高级线程模型&#xff0c;在Java等现代编程语言环境中引入&#xff0c;旨在简化并发编程并…

Vue的图片懒加载 vue-lazyload插件使用

图片懒加载是一种网页性能优化技术&#xff0c;页面加载时仅加载可见区域内的图像&#xff0c;图像只会在用户滚动或浏览到它们时才会被加载&#xff0c;而不是在页面一开始就全部加载。 优点 可以减少首页首次加载的数量&#xff0c;减少服务器的压力有占位图片来展示预加载动…

【面经】操作系统/Linux

1、计算机的五大单元 电脑的五大单元包括&#xff1a;输入单元、输出单元、控制单元、算数逻辑单元、存储单元五大部分。其中CPU占有控制、算术逻辑单元&#xff0c;存储单元又包含内存与辅助内存&#xff1b; 2、什么是操作系统 操作系统&#xff1a;负责管理协调我们计算机…

Qt QStyle详解

1.简介 QStyle类是 Qt 框架中用于控制应用程序界面元素外观的一个抽象基类。这个类提供了一种方式来定制窗口部件&#xff08;widgets&#xff09;的绘制和行为&#xff0c;可以通过改变主题或风格来更改应用程序的外观&#xff0c;而无需修改窗口部件本身的代码。 Qt包含一组…

python爬虫------- Selenium下篇(二十三天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

剑指offer03:数组中重复的数组---leetcode:LCR 120. 寻找文件副本

设备中存有 n 个文件&#xff0c;文件 id 记于数组 documents。若文件 id 相同&#xff0c;则定义为该文件存在副本。请返回任一存在副本的文件 id。 示例 1&#xff1a; 输入&#xff1a;documents [2, 5, 3, 0, 5, 0] 输出&#xff1a;0 或 5提示&#xff1a; 0 ≤ docume…

Python中的定长参数和不定长参数:深入理解与应用

文章目录 1. 定长参数的基本使用2. 不定长参数的基本使用2.1 *args 的使用2.2 **kwargs 的使用 3. 参数的混合使用4. 参数的应用实例4.1 数据处理示例4.2 事件处理示例小彩蛋... 函数参数的灵活处理是编写高效、可读性强的代码的关键。下面将详细介绍定长参数和不定长参数的使用…