Go并发编程:保障安全与解锁奥秘

一、并发安全与锁

1、并发安全
  • 有时候在 Go 代码中可能会存在多个 goroutine 同时操作一个资源(临界区),这种情况会发生竞态问题(数据竞态)
  • 类比现实生活中的例子有十字路口被各个方向的汽车竞争;还有火车上的卫生间被车厢里的人竞争
  • 下面开启两个携程,对变量 x 加一操作,分别加 5000 次,理想结果是 10000,实际三次结果都不相同
package mainimport ("fmt""sync"
)var x int64
var wg sync.WaitGroupfunc add() {for i := 0;i<5000;i++ {x =x +1;}wg.Done()
}func main() {wg.Add(2)go add()go add()wg.Wait()fmt.Println(x)
}/*
第一次结果:6125
第二次结果:6217
第三次结果:10000*/
2、互斥锁 
  • 互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个 goroutine 可以访问共享资源
  • Go 语言中使用 sync 包的 Mutex 类型来实现互斥锁
  • 使用互斥锁来修复上面代码的问题:
package mainimport ("fmt""sync"
)var x int64
var wg sync.WaitGroup
var lock sync.Mutexfunc add() {for i := 0; i < 5000; i++ {lock.Lock() // 加锁x = x + 1lock.Unlock() // 解锁}wg.Done()
}
func main() {wg.Add(2)go add()go add()wg.Wait()fmt.Println(x)   // 10000
}
3、读写互斥锁
  • 互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的
  • 当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下施用读写锁是更好的一种选择
  • 读写锁在 Go 语言中施用 sync 包中的 RWMutex 类型
  • 读写锁分为两种:读锁和写锁
    • 当一个 goroutine获取读锁之后,其他的 goroutine 如果是获取读锁会继续获得锁,如果是获取写锁就会等待
    • 当一个 goroutine 获取写锁之后,其他的 goroutine 无论是获取读锁还是写锁都会等待
  • 注意:是读写锁非常适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势就发挥不出来
package mainimport ("fmt""strconv""sync"
)var m = make(map[string]int)func get(key string) int {return m[key]
}func set(key string, value int) {m[key] = value
}func main() {wg := sync.WaitGroup{}for i := 0; i < 20; i++ {wg.Add(1)go func(n int) {key := strconv.Itoa(n)set(key, n)fmt.Printf("k=:%v,v:=%v\n", key, get(key))wg.Done()}(i)}wg.Wait()
}

二、sync 其他方法

1、sync.WaitGroup
  • 在代码中生硬的使用 time.Sleep肯定是不合适的,Go 语言中可以使用 Sync.WaitGroup来实现并发任务的同步
  • sync.WaitGroup有以下几个方法
方法名功能
(wg * WaitGroup) Add(delta int)计数器+delta
(wg *WaitGroup) Done()计数器-1
(wg *WaitGroup) Wait()阻塞直到计数器变为0
  • sync.WaitGroup内部维护这一个计数器,计数器的值可以增加和减少
  • 例如当我们启动了 N 个并发任务时,就将计数器值增加 N
  • 每个任务完成时通过调用 Done()方法将计数器减 1
  •  通过调用 Wait()来等待并发任务执行完,当计数器值为 0 时,表示所有并发任务已经完成
  • 我们利用 sync.WaitGroup 将上面的代码优化一下:
package mainimport ("fmt""sync"
)var wg sync.WaitGroupfunc hello() {defer wg.Done()fmt.Println("Hello Goroutine!")
}
func main() {wg.Add(1)go hello() // 启动另外一个goroutine去执行hello函数fmt.Println("main goroutine done!")wg.Wait()
}
2、sync.Once
  • 在编程的很多场景下我们需要确保某些操作在高并发的场景下只执行一次,例如只加载一次配置文件,只关闭一次通道等
  • Go 语言中的 sync 包中提供了一个针对值执行一次场景的解决方案-sync.Once
  • sync.Once只有一个 Do 方法,其签名如下:
var icons map[string]image.Imagevar loadIconsOnce sync.Oncefunc loadIcons() {icons = map[string]image.Image{"left":  loadIcon("left.png"),"up":    loadIcon("up.png"),"right": loadIcon("right.png"),"down":  loadIcon("down.png"),}
}// Icon 是并发安全的
func Icon(name string) image.Image {loadIconsOnce.Do(loadIcons)return icons[name]
}

3、Sync.Map

  • Go语言中内置的map不是并发安全的。请看下面的示例
  • 下面的代码开启少量几个goroutine的时候可能没什么问题
  • 当并发多了之后执行上面的代码就会报fatal error: concurrent map writes错误
  • 原因:
    • 因为 map 变量为 指针类型变量,并发写时,多个协程同时操作一个内存
    • 类似于多线程操作同一个资源会发生竞争关系,共享资源会遭到破坏
    • 因此golang 出于安全的考虑,抛出致命错误:fatal error: concurrent map writes
package mainimport ("fmt""strconv""sync"
)var m = make(map[string]int)func get(key string) int {return m[key]
}func set(key string, value int) {m[key] = value
}func main() {wg := sync.WaitGroup{}for i := 0; i < 20; i++ {wg.Add(1)go func(n int) {key := strconv.Itoa(n)set(key, n)fmt.Printf("k=:%v,v:=%v\n", key, get(key))wg.Done()}(i)}wg.Wait()
}
  • 像这种场景下就需要为map加锁来保证并发的安全性了,Go语言的sync包中提供了一个开箱即用的并发安全版map–sync.Map
  • 开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用
  • 同时sync.Map内置了诸如StoreLoadLoadOrStoreDeleteRange等操作方法
package mainimport ("fmt""strconv""sync"
)var m = sync.Map{}func main() {wg := sync.WaitGroup{}for i := 0; i < 20; i++ {wg.Add(1)go func(n int) {key := strconv.Itoa(n)m.Store(key, n)value, _ := m.Load(key)fmt.Printf("k=:%v,v:=%v\n", key, value)wg.Done()}(i)}wg.Wait()
}

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

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

相关文章

使用Microsoft Dynamics AX 2012 - 8. 财务管理

财务管理的主要职责是控制和分析与货币金额有关的所有交易。这些事务发生在整个组织的业务流程中。 因此&#xff0c;财务管理是企业管理解决方案的核心领域。在Dynamics AX中&#xff0c;支持所有部门业务流程的应用程序的深度集成可立即提供准确的财务数据。 分类账交易的原…

K8S(三)—组件

目录 k8s组件控制平面组件&#xff08;Control Plane Componentskube-apiserveretcdkube-schedulerkube-controller-managercloud-controller-managerNode 组件kubelet&#xff08;单独的进程&#xff09;kube-proxy&#xff08;单独的进程&#xff09;容器运行时&#xff08;C…

Redis常问面试题

Redis常问面试题 Redis常问面试题1、Redis 支持哪几种数据类型&#xff1f;2、Redis 做登录是怎么实现的&#xff1f;和传统session有何区别&#xff1f;3、什么是缓存穿透&#xff1f;4、什么是缓存雪崩&#xff1f;5、什么是缓存击穿&#xff1f;6、Redis高可用的几种实现方式…

12.13每日一题(备战蓝桥杯快速排序)

12.13每日一题&#xff08;备战蓝桥杯快速排序&#xff09; 题目 快速排序 给定你一个长度为 n 的整数数列。 请你使用快速排序对这个数列按照从小到大进行排序。 并将排好序的数列按顺序输出。 输入格式 输入共两行&#xff0c;第一行包含整数 n。 第二行包含 n 个整数&…

MySQL 中Relay Log打满磁盘问题的排查方案

MySQL 中Relay Log打满磁盘问题的排查方案 引言&#xff1a; MySQL Relay Log&#xff08;中继日志&#xff09;是MySQL复制过程中的一个重要组件&#xff0c;它用于将主数据库的二进制日志事件传递给从数据库。然而&#xff0c;当中继日志不断增长并最终占满磁盘空间时&…

实操Nginx(4层代理+7层代理)+Tomcat多实例部署,实现负载均衡和动静分离

目录 前言 一、tomcat多实例部署 步骤一&#xff1a;先安装jdk&#xff0c;设置jdk的环境变量&#xff0c;验证是否安装完成&#xff08;192.168.20.8&#xff09; 步骤二&#xff1a;安装tomcat&#xff08;192.168.20.18&#xff09; 步骤三&#xff1a;安装tomcat多实例…

快速上手linux | 一文秒懂Linux各种常用目录命令(上)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 一 、命令提示符和命令的基本格式1.1 如何查看主机名称及修改 二、命令基本格式2.1 命令格式示例2.2 参数的作用…

Spring Cloud gateway - CircuitBreaker GatewayFilte

前面学习Spring cloud gateway的时候&#xff0c;做测试的过程中我们发现&#xff0c;Spring Cloud Gateway不需要做多少配置就可以使用Spring Cloud LoadBalance的功能&#xff0c;比如&#xff1a; spring:application:name: spring-gatewaycloud:gateway:routes:- id: path…

java并发-ReentrantReadWriteLock读写锁

文章目录 介绍读写锁的获取规则示例源码解读ReentrantReadWriteLock核心变量ReentrantReadWriteLock相关属性和构造函数Sync静态内部类的核心属性tryAcquireShared方法tryAcquire方法锁降级 总结 介绍 读写锁就是将一个锁拆分为读锁和写锁两个锁。 读写锁的获取规则 如果有一…

33KB代码实现短网址(php+mysql) V2.0

查立得短网址 V2.0 请保留署名信息;请勿用于非法用途 系统简述&#xff1a;三五十KB代码实现短网址功能前后端都登陆,相对第一版代码已重构。 开发环境&#xff1a;宝塔:linux php Nginx 7.1/mysql5.6;建议环境&#xff1a;linux php 5.4-7.3; 空间域名&#xff1a;域名解析到对…

ELK简单介绍二

学习目标 能够部署kibana并连接elasticsearch集群能够通过kibana查看elasticsearch索引信息知道用filebeat收集日志相对于logstash的优点能够安装filebeat能够使用filebeat收集日志并传输给logstash kibana kibana介绍 Kibana是一个开源的可视化平台,可以为ElasticSearch集群…

电子取证中Chrome各版本解密Cookies、LoginData账号密码、历史记录

文章目录 1.前置知识点2.对于80.X以前版本的解密拿masterkey的几种方法方法一 直接在目标机器运行Mimikatz提取方法二 转储lsass.exe 进程从内存提取masterkey方法三 导出SAM注册表 提取user hash 解密masterkey文件&#xff08;有点麻烦不太推荐&#xff09;方法四 已知用户密…

插入算法(C语言)

#include<cstdio> #include<iostream> #define N 9 using namespace std; int main() {int arr[N1] { 1,4,7,13,16,19,22,25,280 }; int in,i,j;//要插入的数字//打印要插入数字的数组所有元素printf("插入前的数组: ");for ( i 0; i <N; i){print…

STM32G030C8T6:使用外部晶振配置LED灯闪烁

本专栏记录STM32开发各个功能的详细过程&#xff0c;方便自己后续查看&#xff0c;当然也供正在入门STM32单片机的兄弟们参考&#xff1b; 本小节的目标是&#xff0c;使用STM32G030C8T6单片机&#xff0c;通过STM32CubeMX软件&#xff0c;配置并使用外部8MHz晶振&#xff0c;实…

6. Service详解

6. Service详解 文章目录 6. Service详解6.1 Service介绍6.2 Service类型6.3 Service使用6.3.1 实验环境准备6.3.2 ClusterIP类型的Service6.3.3 HeadLess类型的Service6.3.3.1 deployment和statefulset区别6.3.3.2 statefulset deployment 区别 6.3.4 NodePort类型的Service6.…

联邦蒸馏领域中,有哪些有意思的工作

联邦蒸馏领域中&#xff0c;有哪些有意思的工作 王婆卖瓜&#xff0c;自卖自夸。这个博客&#xff0c;我简要向大家推荐一下自己近期在联邦蒸馏方面的研究工作&#xff0c;按照心目中创新度从高到低进行排序&#xff0c;与工作的扎实程度以及发表的会议期刊等级无关。如有不妥…

扩展操作码指令格式

指令 操作码地址码 \quad \quad 判断几地址指令 开头4位不是全1, 则表示是三地址指令 开头4位全1, 后面4位不是全1, 则为二地址指令 前面12全1, 则为零地址指令 当然啦这只是一种扩展方法, 如果想扩展更多, 可以将1110留作扩展操作码 较短的操作码, 我们对它的译码和分析的时间…

测试:HTTP请求中,请求头(Headers)

请求头字段及其用途 在HTTP请求中&#xff0c;请求头&#xff08;Headers&#xff09;是包含了关于客户端环境和请求本身的信息的数据结构&#xff0c;它在每次请求时都会被发送到服务器。 请求头的字段非常多样&#xff0c;以下是一些常见的请求头字段及其用途的详细说明&am…

点云几何 之 判断某一点是否在三角形的边上(3)

点云几何 之 判断某一点是否在三角形的边上&#xff08;3&#xff09; 一、算法介绍二、算法实现1.代码2.结果 总结 一、算法介绍 判断某一点是否在三角形的边上 之前已经介绍了点在三角形的内外判断方法&#xff0c;这里增加点恰好在三角形边上的判断方法 &#xff08;本质上…