Golang并发操作中常见的读写锁详析


Posted in Golang onAugust 30, 2021

互斥锁简单粗暴,谁拿到谁操作。今天给大家介绍一下读写锁,读写锁比互斥锁略微复杂一些,不过我相信我们今天能够把他拿下!

golang读写锁,其特征在于

  • 读锁:可以同时进行多个协程读操作,不允许写操作
  • 写锁:只允许同时有一个协程进行写操作,不允许其他写操作和读操作

读写锁有两种模式。没错!一种是读模式,一种是写模式。当他为写模式的话,作用和互斥锁差不多,只允许有一个协程抢到这把锁,其他协程乖乖排队。但是读模式就不一样了,他允许你多个协程读,但是不能写。总结起来就是:

  • 仅读模式: 多协程可读不可写
  • 仅写模式: 单协程可写不可读

在32位的操作系统中,针对int64类型的值的读和写操作都不可能仅由一个CPU指令来完成。如若一个写操作刚刚执行完第一个指令,就去进行另一个读的协程,这样就会读到一个错误的数据。

下面看个例子吧:

先看主函数:

func main() {
    for i:=0;i<5;i++{
        wg06.Add(1)
        go write(i)
​
        wg06.Add(1)
        go read(i)
    }
    wg06.Wait()
}

每次开辟两条协程,一条协程执行写函数,另一条执行读函数。然后放入等待组。共开辟五次。

在来看一看写函数

func write(i int)  {
    //锁定为仅写模式,其他协程被阻塞
    rwm.Lock()
​
    fmt.Println(i,"writing...")
    <- time.After(10*time.Second)
    fmt.Println("write over!")
​
    rwm.Unlock()
    //解锁仅写模式
    wg06.Done()
}

这个Lock()就是执行读写锁的写模式,当这个模式进行时,只有这条协程能写,其他协程都被阻塞。Unlock()就是解锁这个仅锁模式,等待组中的其他协程不再被阻塞。

再看一看读模式:

func read(i int)  {
    rwm.RLock()
​
    fmt.Println(i,"reading...")
    <-time.After(10 * time.Second)
    fmt.Println(i,"read over!")
​
    rwm.RUnlock()
    wg06.Done()
}

RLock()就是执行读写锁的读模式,执行这个模式其他协程也能读,但是都不能写。

如果程序运行,写协程先抢到锁,所有协程就不能读,只有这条写协程能写,其他人都等着。如果是读协程抢到锁,所以写协程就不可能了,但是读协程仍然可以抢。

现在你知道我们应该什么时候使用读写锁了吗?

在并发进行读写操作时,当读的次数远远超过写的次数的情况下,应该使用读写锁来进行读写并发操作。

Golang读写锁底层原理

在加读锁和写锁的工程中都使用atomic.AddInt32来进行递增,而该指令在底层是会通过LOCK来进行CPU总线加锁的,因此多个CPU同时执行readerCount其实只会有一个成功,从这上面看其实是写锁与读锁之间是相对公平的,谁先达到谁先被CPU调度执行,进行LOCK锁cache line成功,谁就加成功锁

底层实现的CPU指令

底层的2条指令,通过LOCK指令配合CPU的MESI协议,实现可见性和内存屏障,同时通过XADDL则用来保证原子性,从而解决可见性与原子性问题

// atomic/asm_amd64.s TEXT runtime?internal?atomic·Xadd(SB)
    LOCK
    XADDL   AX, 0(BX)

可见性与内存屏障、原子性, 其中可见性通常是指在cpu多级缓存下如何保证缓存的一致性,即在一个CPU上修改了了某个数据在其他的CPU上不会继续读取旧的数据,内存屏障通常是为了CPU为了提高流水线性能,而对指令进行重排序而来,而原子性则是指的执行某个操作的过程的不可分割

总结

到此这篇关于Golang并发操作中常见读写锁的文章就介绍到这了,更多相关Golang并发读写锁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Golang 相关文章推荐
Go各时间字符串使用解析
Apr 02 Golang
go语言中json数据的读取和写出操作
Apr 28 Golang
浅谈Golang 嵌套 interface 的赋值问题
Apr 29 Golang
golang 实现Location跳转方式
May 02 Golang
go mod 安装依赖 unkown revision问题的解决方案
May 06 Golang
再次探讨go实现无限 buffer 的 channel方法
Jun 13 Golang
Go 语言结构实例分析
Jul 04 Golang
Golang 切片(Slice)实现增删改查
Apr 22 Golang
Golang实现可重入锁的示例代码
May 25 Golang
基于Python实现西西成语接龙小助手
Aug 05 Golang
Go中的条件语句Switch示例详解
Aug 23 #Golang
Go Plugins插件的实现方式
Aug 07 #Golang
使用GO语言实现Mysql数据库CURD的简单示例
Aug 07 #Golang
go使用Gin框架利用阿里云实现短信验证码功能
Aug 04 #Golang
手把手教你导入Go语言第三方库
Aug 04 #Golang
Go语言实现Base64、Base58编码与解码
Jul 26 #Golang
golang内置函数len的小技巧
Jul 25 #Golang
You might like
php 无限级数据JSON格式及JS解析
2010/07/17 PHP
php学习之数据类型之间的转换代码
2011/05/29 PHP
php常用文件操作函数汇总
2014/11/22 PHP
php连接oracle数据库的核心步骤
2016/05/26 PHP
PHP安装GeoIP扩展根据IP获取地理位置及计算距离的方法
2016/07/01 PHP
php5.x禁用eval的操作方法
2018/10/19 PHP
laravel框架使用阿里云短信发送消息操作示例
2020/02/15 PHP
幻宇的层模拟窗口效果-提供演示和下载
2007/01/20 Javascript
鼠标划过实现延迟加载并隐藏层的js代码
2013/10/11 Javascript
使用AngularJS处理单选框和复选框的简单方法
2015/06/19 Javascript
基于jquery插件实现拖拽删除图片功能
2020/08/27 Javascript
Angular2中如何使用ngx-translate进行国际化
2017/05/21 Javascript
vue cli 3.x 项目部署到 github pages的方法
2019/04/17 Javascript
记一次用ts+vuecli4重构项目的实现
2020/05/21 Javascript
JSONP解决JS跨域问题的实现
2020/05/25 Javascript
python 数据的清理行为实例详解
2017/07/12 Python
书单|人生苦短,你还不用python!
2017/12/29 Python
python2和python3在处理字符串上的区别详解
2019/05/29 Python
Django中提示消息messages的设置方式
2019/11/15 Python
python的help函数如何使用
2020/06/11 Python
python使用selenium爬虫知乎的方法示例
2020/10/28 Python
使用sublime text3搭建Python编辑环境的实现
2021/01/12 Python
利用CSS3实现进度条的两种姿势详解
2017/03/21 HTML / CSS
使用phonegap获取设备的一些信息方法
2017/03/31 HTML / CSS
翻新二手苹果产品的网络领导者:Mac of all Trades
2017/12/19 全球购物
客户服务经理岗位职责
2014/01/29 职场文书
幼儿园中秋节活动方案
2014/02/06 职场文书
消防战士优秀事迹材料
2014/02/13 职场文书
年度优秀员工获奖感言
2014/08/15 职场文书
运动会广播稿200米(5篇)
2014/10/15 职场文书
《风筝》教学反思
2016/02/23 职场文书
2019请假条的基本格式及范文!
2019/07/05 职场文书
让人感觉高大上的讲话稿怎么写?
2019/07/08 职场文书
python 爬取哔哩哔哩up主信息和投稿视频
2021/06/07 Python
python获取字符串中的email
2022/03/31 Python
Windows Server 2016服务器用户管理及远程授权图文教程
2022/08/14 Servers