Go语言开发保证并发安全实例详解
作者:Sundar84034 发布时间:2024-02-21 10:19:48
什么是并发安全?
在高并发场景下,进程、线程(协程)可能会发生资源竞争,导致数据脏读、脏写、死锁等问题,为了避免此类问题的发生,就有了并发安全。
这里举一个简单的例子:
var data int
go func() {
data++
}()
if data == 0 {
fmt.Printf("the value is %v.\n", data)
}
在这段代码中
第2行go关键字开启了一个新的协程,来执行data++操作
第5行,对data变量进行了读取判断的操作
以上两部是由2个不同线程/协程运行,且没有任何措施保证执行顺序,所以执行结果是不确定的。
没有输出。(第3行是在第5行之前执行的)
输出 the value is 0。(第5行和第6行在第3行之前执行)
输出 the value is 1。(第5行在第3行之前执行,但第3行在第6行之前执行)
Go如何保证并发安全
目前了解到的,大概有这3种,Mutex、Channel、Atomic
Mutex
加锁应该是最常见的并发控制方法,一般分成两种,乐观锁和悲观锁。
锁是由操作系统的调度器来实现的,锁通常用来保护一段逻辑,
悲观锁
悲观锁是一种悲观思想,它总认为最坏的情况可能会出现。不管意料之外的结果是否会发生,只要存在发生的可能,就在操作这个资源之前先上锁。例如互斥锁、读写锁都是悲观锁。
在go中,除了automic,其它都是悲观锁
悲观锁应该都是由操作系统的调度器来实现的,通常用来保护一段逻辑,主要是通过阻塞其它线程,保证当前时刻只有一个线程在对资源进行操作,因此性能相对较差,浪费了计算机多核的优势。
乐观锁
乐观锁的思想与悲观锁的思想相反,它总认为资源和数据不会被别人所修改,所以读取不会上锁,但是乐观锁在进行写入操作的时候会判断当前数据是否被修改过。
乐观锁的实现方案主要包含CAS和版本号机制。
乐观锁适用于多读的场景,可以提高吞吐量。
版本号机制
通过在数据表中,增加一个版本号字段,当数据发生更新时,版本号值发生改变。 例如一个线程A想要更新变量s的值,在读取s的值的同时读取版本号,在提交更新时,用之前读到的版本号值与当前的版本号值进行比对,当且仅当版本号值一致时,才会触发更新,否则不断进行重试,直到更新成功。
CAS
CAS全名为Compare And Swap,即比较与转换,是一种有名的无锁算法。在不使用锁的情况下,实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步,
互斥锁
GO使用Sync包的Mutex类型来实现互斥锁,它能保证同时只有一个goroutine可以访问资源。
func sample() {
var l sync.Mutex
l.Lock()
defer l.Unlock()
// so something
}
读写互斥锁
GO使用Sync包的RWMutex类型来实现互斥锁。当我们去并发的读取一个资源,只要数据没有发生写入,是没必要加锁的。因此读多写少的情况下,使用读写互斥锁是更好的选择,性能更好。
读写锁分为两种:读锁和写锁。
当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁可以顺利获得,如果是获取写锁就会等待;
当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。
package main
import (
"fmt"
"sync"
"time"
)
var (
wg sync.WaitGroup
rwlock sync.RWMutex
)
func write() {
rwlock.Lock() // 加写锁
time.Sleep(10 * time.Millisecond)
rwlock.Unlock() // 解写锁
wg.Done()
}
func read() {
rwlock.RLock() // 加读锁
time.Sleep(time.Millisecond)
rwlock.RUnlock() // 解读锁
wg.Done()
}
func main() {
start := time.Now()
//读多
for i := 0; i < 1000; i++ {
wg.Add(1)
go read()
}
//写少
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
wg.Wait()
end := time.Now()
fmt.Println(end.Sub(start))
}
来源:https://juejin.cn/post/7025797208209358862
猜你喜欢
- 本文实例讲述了Python简单定义与使用字典的方法。分享给大家供大家参考,具体如下:#coding=utf8print ''
- php遍历一个文件夹内的所有文件和文件夹,并删除所有文件夹和子文件夹下的所有文件的代码,通过递归方式实现达到清空一个目录的效果,代码简单实用
- 我一直使用Microsoft的FrontPage 98来开发ASP/ADO之类的Internet数据库应用程序。现在我听说许多人都非常信奉采
- 目录1.jupyter简介2.jupyter基础操作2.1windows更新pip库2.2jupyter安装2.3初次启动jupyter2.
- 执行环境会负责管理代码执行过程中使用的内存,编写JavaScript程序时,所需内存的分配以及无用内存的回收完全实现自动管理。原理:找出那些
- 编辑PyCharm安装目录下PyCharm 4.5.3\bin下的pycharm.exe.vmoptions文件, 如下-server-Xm
- 前言用过Django的小伙伴都知道,Django的ORM是自带的,比较特殊,而且集成了很多功能,比如数据库迁移… 何为ORM,个人之见解,简
- Flask 框架中如果想要实现WebSocket功能有许多种方式,运用SocketIO库来实现无疑是最简单的一种方式,Flask中封装了一个
- 引言这两天遭遇了手机号登录相关的压测需求,算是比较棘手的。主要原因有两个,第一:之前从来没有接手过这个项目,不熟悉各种规则;第二:数据量偏大
- 前言matplotlib.pyplot是一些命令行风格函数的集合,使matplotlib以类似于MATLAB的方式工作。每个pyplot函数
- 写在前面QQ群里偶然看到群友问这个问题, pandas读取大文件时怎么才能获取进度? 我第一反应是: 除非pandas的read_excel
- 分形几何学的基本思想:客观事物具有自相似性的层次结构,局部和整体在形态,功能,信息,时间,空间等方面具有统计意义上的相似性,称为自相似性,自
- execute、executeUpdate、executeQuery三者的区别(及返回值)一、boolean execute(String
- ImageDataGenerator位于keras.preprocessing.image模块当中,可用于做数据增强,或者仅仅用于一个批次一
- 情景互动广告是指需要广告画面外的物体来参与的广告,例如:ps:这是我收集的,大家慢慢看吧!其中有大多数收集http://www.netnoe
- 1.排序示例表内容见此篇文章Mysql支持数据排序操作,例如,现在我们按照工资从小到大进行排序操作:mysql> select ena
- 很多人都使用很多的编程工具,尤其对于Web开发人员。这个小教程将告诉各位如何使DreamweaverMX编程环境适合中国的Web开发人员。一
- 简介:fixture区别于unnitest的传统单元测试(setup/teardown)有显著改进:1.有独立的命名,并通过声明它们从测试函
- 在python中,如下代码结果一定不会让你吃惊:Python 3.3.2 (v3.3.2:d047928ae3f6, May 16 2013
- 1、从记录中选出所有fault_code列的值在fault_list= [487, 479, 500, 505]这个范围内的记录r