深入理解Go语言中的并发编程【29】【原子操作(atomic包)、并发安全性】

news2024/11/17 15:54:42

文章目录

  • 原子操作(atomic包)
      • 原子操作
      • atomic包
      • 示例
    • 并发安全性


原子操作(atomic包)

原子操作

代码中的加锁操作因为涉及内核态的上下文切换会比较耗时、代价比较高。针对基本数据类型我们还可以使用原子操作来保证并发安全,因为原子操作是Go语言提供的方法它在用户态就可以完成,因此性能比加锁操作更好。Go语言中原子操作由内置的标准库sync/atomic提供。

atomic包

http://doc.golang.ltd/
方法解释
func LoadInt32(addr *int32) (val int32) func LoadInt64(addr *int64) (val int64) func LoadUint32(addr*uint32) (val uint32) func LoadUint64(addr*uint64) (val uint64) func LoadUintptr(addr*uintptr) (val uintptr) func LoadPointer(addr*unsafe.Pointer) (val unsafe.Pointer)读取操作
func StoreInt32(addr *int32, val int32) func StoreInt64(addr *int64, val int64) func StoreUint32(addr *uint32, val uint32) func StoreUint64(addr *uint64, val uint64) func StoreUintptr(addr *uintptr, val uintptr) func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)写入操作
func AddInt32(addr *int32, delta int32) (new int32) func AddInt64(addr *int64, delta int64) (new int64) func AddUint32(addr *uint32, delta uint32) (new uint32) func AddUint64(addr *uint64, delta uint64) (new uint64) func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)修改操作
func SwapInt32(addr *int32, new int32) (old int32) func SwapInt64(addr *int64, new int64) (old int64) func SwapUint32(addr *uint32, new uint32) (old uint32) func SwapUint64(addr *uint64, new uint64) (old uint64) func SwapUintptr(addr *uintptr, new uintptr) (old uintptr) func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)交换操作
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool) func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool) func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool) func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)比较并交换操作

示例

我们填写一个示例来比较下互斥锁和原子操作的性能。

var x int64
var l sync.Mutex
var wg sync.WaitGroup

// 普通版加函数
func add() {
    // x = x + 1
    x++ // 等价于上面的操作
    wg.Done()
}

// 互斥锁版加函数
func mutexAdd() {
    l.Lock()
    x++
    l.Unlock()
    wg.Done()
}

// 原子操作版加函数
func atomicAdd() {
    atomic.AddInt64(&x, 1)
    wg.Done()
}

func main() {
    start := time.Now()
    for i := 0; i < 10000; i++ {
        wg.Add(1)
        // go add()       // 普通版add函数 不是并发安全的
        // go mutexAdd()  // 加锁版add函数 是并发安全的,但是加锁性能开销大
        go atomicAdd() // 原子操作版add函数 是并发安全,性能优于加锁版
    }
    wg.Wait()
    end := time.Now()
    fmt.Println(x)
    fmt.Println(end.Sub(start))
}

atomic包提供了底层的原子级内存操作,对于同步算法的实现很有用。这些函数必须谨慎地保证正确使用。除了某些特殊的底层应用,使用通道或者sync包的函数/类型实现同步更好。

并发安全性

  多协程并发修改同一块内存,产生资源竞争。go run或go build时添加-race参数检查资源竞争情况。
  n++不是原子操作,并发执行时会存在脏写。n++分为3步:取出n,加1,结果赋给n。测试时需要开1000个并发协程才能观察到脏写。

在这里插入图片描述

func atomic.AddInt32(addr *int32, delta int32) (new int32)
func atomic.LoadInt32(addr *int32) (val int32)

  把n++封装成原子操作,解除资源竞争,避免脏写。

var lock sync.RWMutex		//声明读写锁,无需初始化
lock.Lock() lock.Unlock()	//加写锁和释放写锁
lock.RLock() lock.RUnlock()	//加读锁和释放读锁

  任意时刻只可以加一把写锁,且不能加读锁。没加写锁时,可以同时加多把读锁,读锁加上之后不能再加写锁。

package main

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

var n int32 = 0
var lock sync.RWMutex

func inc1() {
	n++ //n++不是原子操作,它分为3步:取出n,加1,结果赋给n
}

func inc2() {
	atomic.AddInt32(&n, 1) //封装成原子操作
}

func inc3() {
	lock.Lock()   //加写锁
	n++           //任一时刻,只有一个协程能进入临界区域
	lock.Unlock() //释放写锁
}

func main() {
	const P = 1000 //开大量协程才能把脏写问题测出来
	wg := sync.WaitGroup{}
	wg.Add(P)
	for i := 0; i < P; i++ {
		go func() {
			defer wg.Done()
			inc1()
		}()
	}
	wg.Wait()
	fmt.Printf("finally n=%d\n", n) //多运行几次,n经常不等于1000
	fmt.Println("===========================")
	n = 0 //重置n
	wg = sync.WaitGroup{}
	wg.Add(P)
	for i := 0; i < P; i++ {
		go func() {
			defer wg.Done()
			inc2()
		}()
	}
	wg.Wait()
	fmt.Printf("finally n=%d\n", atomic.LoadInt32(&n))
	fmt.Println("===========================")
	n = 0 //重置n
	wg = sync.WaitGroup{}
	wg.Add(P)
	for i := 0; i < P; i++ {
		go func() {
			defer wg.Done()
			inc3()
		}()
	}
	wg.Wait()
	lock.RLock() //加读锁。当写锁被其他协程持有时,加读锁操作将被阻塞;否则,如果其他协程持有读锁,加读锁操作不会被阻塞
	fmt.Printf("finally n=%d\n", n)
	lock.RUnlock() //释放读锁
	fmt.Println("===========================")
}

  数组、slice、struct允许并发修改(可能会脏写),并发修改map有时会发生panic。如果需要并发修改map请使用sync.Map。

package main

import (
	"fmt"
	"sync"
)

type Student struct {
	Name string
	Age  int32
}

var arr = [10]int{}
var m = sync.Map{}

func main() {
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() { //写偶数位
		defer wg.Done()
		for i := 0; i < len(arr); i += 2 {
			arr[i] = 0
		}
	}()
	go func() { //写奇数位
		defer wg.Done()
		for i := 1; i < len(arr); i += 2 {
			arr[i] = 1
		}
	}()
	wg.Wait()
	fmt.Println(arr) //输出[0 1 0 1 0 1 0 1 0 1]
	fmt.Println("=======================")
	wg.Add(2)
	var stu Student
	go func() {
		defer wg.Done()
		stu.Name = "Fred"
	}()
	go func() {
		defer wg.Done()
		stu.Age = 20
	}()
	wg.Wait()
	fmt.Printf("%s %d\n", stu.Name, stu.Age)//Fred 20
	fmt.Println("=======================")
	wg.Add(2)
	go func() {
		defer wg.Done()
		m.Store("k1", "v1")// 往map写数据
	}()
	go func() {
		defer wg.Done()
		m.Store("k1", "v2")
	}()
	wg.Wait()
	fmt.Println(m.Load("k1"))//从map读数据--》v1 true
}

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

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

相关文章

iOS 应用上架的步骤和工具简介

APP开发助手是一款能够辅助iOS APP上架到App Store的工具&#xff0c;它解决了iOS APP上架流程繁琐且耗时的问题&#xff0c;帮助跨平台APP开发者顺利将应用上架到苹果应用商店。最重要的是&#xff0c;即使没有配置Mac苹果机&#xff0c;也可以使用该工具完成一系列操作&#…

C#实现滑动拼图验证码

开发环境&#xff1a;C#&#xff0c;VS2019&#xff0c;.NET Core 3.1&#xff0c;ASP.NET Core 1、建立一个验证码控制器 新建两个方法Create和Check&#xff0c;Create用于创建验证码&#xff08;返回2张图片和令牌&#xff09;&#xff0c;Check用于验证&#xff08;验证图…

EXCEL数据自动web网页查询----高效工作,做个监工

目的 自动将excel将数据填充到web网页,将反馈的数据粘贴到excel表 准备 24KB的鼠标连点器软件(文末附链接)、Excel 宏模块 优势 不需要编程、web验证、爬虫等风险提示。轻量、稳定、安全。 缺点 效率没那么快 演示 宏环境 http://t.csdn.cn/DRAC2 宏按钮

idea导入maven项目问题

问题产生原因&#xff1a; ①idea加载maven项目&#xff0c;如果网络不通畅&#xff0c;会在maven仓库中产生一个文件&#xff0c;如下图所示: ②当网络通畅时&#xff0c;在下载就会因为此文件导致无法下载正确的maven依赖 解决方案&#xff1a; ①打开maven仓库的根目录 ②…

(学习笔记-内存管理)虚拟内存

单片机是没有操作系统的&#xff0c;每次写完代码&#xff0c;都需要借助工具把程序烧录进去&#xff0c;这样程序才能跑起来。另外&#xff0c;单片机的CPU是直接操作内存的[物理地址]。 在这种情况下&#xff0c;要想在内存中同时运行两个程序是不可能的。如果第一个程序在 2…

骨传导耳机和入耳式耳机哪个好?骨传导耳机适合什么人群使用

骨传导耳机和入耳式耳机哪个好&#xff0c;这个问题&#xff0c;首先我们先了解一下骨传导耳机的对比入耳式耳机的优势有哪些&#xff1a; &#xff08;1&#xff09;健康 骨传导耳机因为是利用骨振动原理&#xff0c;完全不需要经过我们耳膜进行传输&#xff0c;可以有效缓解…

Selenium API基础 8种定位

id定位 class定位 xpath定位 css定位 link partial_link模糊匹配 tag name

Redis 哨兵 (sentinel)

是什么 官网理论&#xff1a;https://redis.io/docs/management/sentinel/ 吹哨人巡查监控后台 master 主机是否故障&#xff0c;如果故障了根据投票数自动将某一个从库转换为新主库&#xff0c;继续对外服务。 作用&#xff1a;无人值守运维 哨兵的作用&#xff1a; 1…

index页面通过<script>引入根目录下的js文件后,vite打包项目后,项目中无js文件解决方法

解决方法&#xff1a; 根据打包报错提示&#xff0c;如图&#xff1a;即在<script>标签中加入&#xff1a;type&#xff0c;如图&#xff1a; 再打包 js文件就会被打包进去&#xff01;

华为认证 | HCIE考过了,证书编号怎么查?

我们都知道&#xff0c;每一个HCIE都拥有属于自己的独一无二的编号&#xff0c;这个编号到底是指什么呢&#xff1f; HCIE作为华为专家级的认证&#xff0c;考试难度极高&#xff0c;你要通过HCIE的笔试、实验LAB考试。 当你“过五关斩六将”&#xff0c;通过LAB的那一刻&…

手风琴案例(jQuery)

案例效果 代码实现 jQuery代码&#xff08;两种方法&#xff09; 方法一&#xff1a;hover版 $(function () {$(".king li").hover(function() {$(this).addClass("current").siblings().removeClass("current");}, function() {$(".king…

DDSv1.4规范(中文版)

实时数据分发 (DDS) V1.4 (2015-04-10正式发布) https://www.omg.org/spec/DDS/1.4/PDF http://www.omg.org/spec/DDS/20140501/dds_dcps.idl

vue3-eslint-prettier-czgit配置

vue3 eslint prettier cz-git 一&#xff1a;vue3 1.1 vue3创建 输入命令后根据提示选择&#xff0c;项目是ts所以必选typescript pnpm create vite1.2 安装依赖 pnpm i1.3 运行 pnpm run dev二&#xff1a;配置eslint 2.1 执行安装命令 pnpm add eslint -D2.2 初始化…

POI 导出 树形结构

参考文章&#xff1a;(327条消息) Excel树状数据绘制导出_excel导出树形结构_Deja-vu xxl的博客-CSDN博客https://blog.csdn.net/weixin_45873182/article/details/120132409?spm1001.2014.3001.5502 Overridepublic void exportPlus(String yearMonth, HttpServletRequest re…

SQL-每日一题【627. 变更性别】

题目 Salary 表&#xff1a; 请你编写一个 SQL 查询来交换所有的 f 和 m &#xff08;即&#xff0c;将所有 f 变为 m &#xff0c;反之亦然&#xff09;&#xff0c;仅使用 单个 update 语句 &#xff0c;且不产生中间临时表。 注意&#xff0c;你必须仅使用一条 update 语句…

Bad owner or permissions on ~/.ssh/config

错误原因&#xff1a;设置本地所有文件的权限为741等。。。 在执行ssh免密码登录时报如下的错误&#xff1a;Bad owner or permissions on ~/.ssh/config。 解决方案&#xff1a; chmod 600 ~/.ssh/config

MySQL笔记——数据库当中的事务以及Java实现对数据库进行增删改查操作

系列文章目录 MySQL笔记——MySQL数据库介绍以及在Linux里面安装MySQL数据库&#xff0c;对MySQL数据库的简单操作&#xff0c;MySQL的外接应用程序使用说明 MySQL笔记——表的修改查询相关的命令操作 MySQL案例——多表查询以及嵌套查询 文章目录 系列文章目录 前言 一…

OpenLayers入门,html原生网页如何使用OpenLayers地图

专栏目录: OpenLayers入门教程汇总目录 前言 尽管现在大部分网页都是使用vue或者react开发了,但是还是有不少开发者使用的是网页原生html进行开发,或者是老项目维护的需要,所以为了照顾使用html原生网页的同学们,本章简单讲解一下如何使用原始html网页情况下使用OpenLay…

网站服务器出错的原因分析和解决方法

​  网站在日常运行的过程中&#xff0c;难免会遇见一些问题&#xff0c;这次我们就来分析关于网站服务器出错、服务器异常的原因以及如何解决网站服务器错误的方法。 如何知道是网站服务器的问题呢? 只要网站不能正常访问运行&#xff0c;那么一定会反馈相关的错误代码和原…

Ubuntu18.04下编译qgc源码

写在前面 在下载前必须说明&#xff0c;根据你的qgc源码版本进行下载&#xff0c;有的源码必须要求Qt是5.15版本以上。 个人所使用开发软件 版本QT5.12.9qgc源码V4.0Ubuntu18.04 QT下载 &#xff08;1&#xff09;我们可以去官网下载官网下载地址具体的下载方法这里不用多说&a…