【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,一经查实,立即删除!

相关文章

【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;等三个角度进行了说明和演…

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

挖掘目标的部署在微信的资产(减少信息的收集&#xff0c;毕竟一般web站点没有账号密码不好进入后台&#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;一定要天天…

CAN帧中的ACK位

1&#xff1a;先看官方文档对ACK的解释 All nodes that have received the matching CRC sequence (and, in FD Frames the matching stuff count) shall send an ACK within the ACK slot by overwriting the recessive bit of the transmitter by a dominant bit (they send…

求圆、圆球和圆柱的面积和体积(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h> //定义π常量的值&#xff1b; # define π 3.141526int main() {//初始化变量值&#xff1b;float r, h, S1, S2, P1, V1, V2;int judge 0;//提示用户&#x…

Python基于flask的豆瓣电影分析可视化系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

mid_360建图和定位

录制数据 roslaunch livox_ros_driver2 msg_MID360.launch使用fast-lio 建图 https://github.com/hku-mars/FAST_LIO.git 建图效果 使用python做显示 https://gitee.com/linjiey11/mid360/blob/master/show_pcd.py 使用 point_lio建图 https://github.com/hku-mars/Point…

【数据结构】【C++】AVL树的模拟实现(插入、判断、旋转)

文章目录 1 概念2 实现2.1 AVL树结点的定义2.2 AVL树的插入2.2.1 AVL树的插入规则2.2.2 旋转2.2.2.1 左单旋2.2.2.2 右单旋2.2.2.3 左右双旋2.2.2.4 右左双旋 2.2.3 总结 3 平衡判断4 删除5 源码 1 概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二…

找不到api-ms-win-crt-runtime-l1-1-0.dll文件5种解决方法

在日常使用计算机的过程中&#xff0c;我们时常会遭遇各类意想不到的问题&#xff0c;其中之一就是“api-ms-win-crt-runtime-l1-1-0.dll丢失”。这个错误通常发生在Windows操作系统中&#xff0c;它表示一个动态链接库文件丢失或损坏。这个问题可能会导致某些应用程序无法正常…

1Panel官方出品丨MaxKB:基于LLM大模型的知识库问答系统

1Panel&#xff08;github.com/1Panel-dev/1Panel&#xff09;是一款现代化、开源的Linux服务器运维管理面板&#xff0c;它致力于通过开源的方式&#xff0c;帮助用户简化建站与运维管理流程。为了方便广大用户快捷安装部署相关软件应用&#xff0c;1Panel特别开通应用商店&am…

TensorFlow 1.x的学习

.为什么还有很多人都选择使用TensorFlow 1.x 兼容性问题: TensorFlow 1.x在一些旧项目中已经得到了广泛应用&#xff0c;这些项目可能依赖于1.x版本的特定API或行为。升级到2.x可能需要大量的代码修改和测试工作&#xff0c;对于一些已经稳定运行的项目&#xff0c;维护者可能…

实现iOS App代码混淆

简介 在开发iOS应用程序时&#xff0c;保护代码安全是至关重要的。代码混淆是一种常用的技术&#xff0c;可以增加逆向工程的难度&#xff0c;防止他人对代码的篡改和盗用。本文将介绍如何实现iOS App代码混淆的步骤和操作方法。 整体流程 下面是实现iOS App代码混淆的整体流…

创维:在博鳌论坛 叩响世界之门

出走半生&#xff0c;归来仍是少年。 2024年4月8日&#xff0c;一个离开海南近半个世纪的“少年”回到琼海博鳌&#xff0c;“下一站&#xff0c;1000亿&#xff01;”&#xff0c;他的承诺掷地有声。“1000亿”&#xff0c;意指创维集团在2025年前冲击千亿营收&#xff0c;这…

【JavaWeb】Day45.Mybatis——入门程序

什么是MyBatis? MyBatis是一款优秀的持久层框架&#xff0c;用于简化JDBC的开发。 &#xff08;持久层&#xff1a;指的是就是数据访问层(dao)&#xff0c;是用来操作数据库的。&#xff09; &#xff08;框架&#xff1a;是一个半成品软件&#xff0c;是一套可重用的、通用…