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

import (
	"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 main

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

func main() {
	var adds atomic.Uint64
	var subs atomic.Uint64
	var wg sync.WaitGroup

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

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

func main() {
	var ops atomic.Uint64
	var stopFlag atomic.Bool
	var wg sync.WaitGroup

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

import (
	"fmt"
	"log"
	"os"
	"sync"
)

var (
	logger   *log.Logger
	logMutex 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.WaitGroup

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

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

var requestCount atomic.Int64

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

import (
	"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)
			return
		default:
			fmt.Printf("Worker %d is working\n", id)
			time.Sleep(time.Second)
		}
	}
}

func main() {
	var wg sync.WaitGroup
	ctx, 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)
			return
		default:
			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. 原子操作的用途

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

飞机飞行数据三维可视化管控系统更智能、精准

近年来&#xff0c;随着无人化工厂和智能工厂在中国大量涌现&#xff0c;基于成熟的数字孪生理念&#xff0c;智能工厂三维可视化虚拟管控系统引领未来工业革命的先锋。数字孪生公司深圳华锐视点结合前沿的三维仿真、GIS和三维可视化技术技术&#xff0c;深度集成工厂生产、经营…

鸿蒙 Failed :entry:default@CompileResource...

Failed :entry:defaultCompileResource... media 文件夹下有文件夹或者图片名称包含中文字符 rawfile 文件夹下文件名称、图片名称不能包含中文字符

IGBT退饱和现象解析与防范

IGBT是一种重要的功率半导体器件&#xff0c;广泛应用于电力电子领域&#xff0c;如变频器、电动机驱动、电力传输等。在这些应用中&#xff0c;IGBT的导通和关断特性至关重要&#xff0c;而退饱和是IGBT工作过程中的一个重要现象。 IGBT的退饱和定义 退饱和是指IGBT在导通状态…

软件测试面试题分享(含答案+文档)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 准备找工作的小伙伴们&#xff0c;今天我给大家带来了一些自动化测试面试题&#xff0c;在这个公…

NLP vs. LLMs: 理解它们之间的区别

作者&#xff1a;Elastic Platform Team 随着人工智能持续发展并在无数行业解决问题&#xff0c;技术的一个关键部分是能够无缝地桥接人类语言和机器理解之间的差距。这就是自然语言处理&#xff08;NLP&#xff09;和大型语言模型&#xff08;LLMs&#xff09;的用武之地。它们…

运用OSI模型提升排错能力

1. OSI模型有什么实际的应用价值&#xff1f; 2. 二层和三层网络的区别和应用&#xff1b; 3. 如何通过OSI模型提升组网排错能力&#xff1f; -- OSI - 开放式系统互联 - 一个互联标准 - 从软件和硬件 定义标准 - 不同厂商的设备 研发的技术 - 具备兼容性 -- O…

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

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

初识集合框架

前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; hellohello~&#xff0c;大家好&#x1f495;&#x1f495;&#xff0c;这里是E绵绵呀✋✋ &#xff0c;如果觉得这篇文章还不错的话还请点赞❤️❤️收藏&#x1f49e; &#x1f49e; 关注&#x1f4a5;&#x1f…

机器视觉图像采集卡及其接口概述

本文主要是介绍机器视觉图像采集卡及其使用的各种接口。 首先&#xff0c;我们将概述外围计算机卡&#xff0c;然后探索图像采集卡中使用的不同类型的机器视觉接口。 我们先来说一个常见的问题&#xff1a;什么是电脑外设卡&#xff0c;如何分类&#xff1f; 用于机器视觉的…

GIS 数据格式转换

1、在线工具 mapshaper 2、数据上传 3、数据格式转换 导入数据可导出为多种格式&#xff1a;Shapefile、Json、GeoJson、CSV、TopJSON、KML、SVG

【python】项目实战

启动一个项目对于新手都是不容易的事情 在哪 对于Windows平台&#xff0c;打开cmd 使用命令py -0p 【其中0是零】 显示已安装的 python 版本且带路径的列表 切换python3命令 在Windows下&#xff0c;可以使用cmd下使用mklink命令创建“软链接”更好一些。 例如&#xf…

Three.js--》穿越虚拟门槛打造的3D登录界面

今天简单实现一个three.js的小Demo&#xff0c;加强自己对three知识的掌握与学习&#xff0c;只有在项目中才能灵活将所学知识运用起来&#xff0c;话不多说直接开始。 目录 项目搭建 初始化three代码 添加背景与地球 星星动画效果 星云动画效果 实现登录框效果 项目搭建…

软件设计不是CRUD(18):像搭积木一样搭建应用系统(上)——单个应用系统的搭建过程

1、概述 之前的文章本专题花了大量文字篇幅,介绍如何基于业务抽象的设计方式完成应用系统各个功能模块的设计工作。而之所以进行这样的功能模块设计无非是希望这些功能模块在具体的项目实施过程中,能够按照当时的需求快速的、简易的、稳定的、最大可能节约开发成本的形成可用…

提高大型语言模型 (LLM) 性能的四种数据清理技术

原文地址&#xff1a;four-data-cleaning-techniques-to-improve-large-language-model-llm-performance 2024 年 4 月 2 日 检索增强生成&#xff08;RAG&#xff09;过程因其增强对大语言模型&#xff08;LLM&#xff09;的理解、为它们提供上下文并帮助防止幻觉的潜力而受…

关于沃进科技无线模块demo软件移植问题

文章目录 一、无线模块开发测试准备二、开发板硬件三、开发板默认功能上电默认界面功能选择界面数据包发送界面数据包接收显示界面射频性能测试界面参数设置界面固件信息显示界面 四、软件开发软件SDK框图1、射频硬件驱动&#xff08;详见./radio/myRadio_gpio.c&#xff09;2、…

Linux_iptables防火墙学习笔记

文章目录 iptables 概述四表五链iptables 安装启动iptables 配置详解iptables配置文件iptables配置语法iptables常用实例查看规则修改默认规则保存和备份规则恢复备份的规则清空规则放行SSH服务在ubuntu14.04中iptables规则持久化 iptables 概述 主机型 对主机进行保护 网络型…

CSS基础:margin属性4种值类型,4个写法规则详解

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 Web 开发工具合集 268篇…

pnpm 使用 workspace 报错 ERR_INVALID_THIS

有时候真的感觉如果有一个老师指路&#xff0c;那么遇到的坑真的会少很多。 错误示例&#xff1a; GET https://registry.npmjs.org/rollup%2Fplugin-typescript error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.原因是什么&#xff1f;原因就是 pnpm 的…

Web中使用Weblogic用户

WebLogic用户&#xff0c;组设置 1. 登录weblogic console, domain结构中选择Security Realms&#xff0c;显示安装时默认创建的Realm &#xff1a; myrealm 2. 点击myrealm, 选择 users and Group&#xff0c; 追加用户和组 选择既存的权限组追加到新规的组中&#xff0c;赋予…