以太坊系列之四: 使用atomic来避免lock

简介: 使用atomic来避免lock在程序中为了互斥,难免要用锁,有些时候可以通过使用atomic来避免锁,从而更高效.下面给出一个以太坊中的例子,就是MsgPipeRW,从名字Pipe可以看出,他实际上就是一个pipe,相比大家对pipe已经比较熟悉了,我就不多解释了.

使用atomic来避免lock

在程序中为了互斥,难免要用锁,有些时候可以通过使用atomic来避免锁,
从而更高效.

下面给出一个以太坊中的例子,就是MsgPipeRW,从名字Pipe可以看出,
他实际上就是一个pipe,相比大家对pipe已经比较熟悉了,我就不多解释了.

type MsgPipeRW struct {
    w       chan<- Msg
    r       <-chan Msg
    closing chan struct{}
    closed  *int32
}

//创建一个MsgPipeRw
func MsgPipe() (*MsgPipeRW, *MsgPipeRW) {
    var (
        c1, c2  = make(chan Msg), make(chan Msg)
        closing = make(chan struct{})
        closed  = new(int32)
        rw1     = &MsgPipeRW{c1, c2, closing, closed}
        rw2     = &MsgPipeRW{c2, c1, closing, closed}
    )
    return rw1, rw2
}
pipe就像水管一样,这里MsgPipe创建了两根水管,可以自由双向流动,rw1写,rw2就可以
读到,rw2写,rw1就可以读到.原理也很简单,因为rw1写和rw2操作的是同一个chan Msg,反之亦然.

关键是这里的closed,可以想想rw1,rw2很有可能在不同的goroutine发生读写关闭等操作,
这时候要同时访问closed这个变量,难免会发生冲突,我们看看如何避免.

closed如果为0表示没有关闭,1表示已经关闭,就不应该再进行读写了.
// 从pipe中读取一个msg
func (p *MsgPipeRW) ReadMsg() (Msg, error) {
//这里不能直接*p.closed==0,要使用atomic.LoadInt32来访问
    if atomic.LoadInt32(p.closed) == 0 {
        ...
    }
    return Msg{}, ErrPipeClosed
}

// 写的时候也一样
func (p *MsgPipeRW) WriteMsg(msg Msg) error {
    if atomic.LoadInt32(p.closed) == 0 {
        ...
    }
    return ErrPipeClosed
}

读写消息只是读取互斥变量,没有发生写入,下面来看看close的时候如何写入

func (p *MsgPipeRW) Close() error {
    if atomic.AddInt32(p.closed, 1) != 1 { //避免锁,
        // someone else is already closing
        atomic.StoreInt32(p.closed, 1) // avoid overflow
        return nil
    }
    close(p.closing)
    return nil
}

atomic.AddInt32能够避免我们一般这样的写法发生的并发访问.

if *p.closed==0 {
    *p.closed+=1
}

感兴趣的可以修改代码试试,采用*p.closed==0这种方式,会不会造成崩溃,测试代码如下

func TestMsgPipeConcurrentClose(t *testing.T) {
    rw1, _ := MsgPipe()
    for i := 0; i < 10; i++ {
        go rw1.Close()
    }
}

atomic看似神奇的避免了锁,实际上这需要处理器的特殊指令支持,尤其是发生在多和处理器上时,atomic指令
会保证对特定地址的锁定.
atomic相对于lock的最大优势就是他只是一条特殊指令,不用发生系统上下文切换,我们都知道系统上下文切换
代价要大得多.

目录
相关文章
|
JSON 缓存 应用服务中间件
开源API网关APISIX源码分析(一)
开源API网关APISIX源码分析
487 0
|
存储 缓存 网络协议
你只会用 Java Sockets?推荐 11 个开源的 Java Socket 框架
你只会用 Java Sockets?推荐 11 个开源的 Java Socket 框架
2144 0
|
druid Oracle 关系型数据库
奇奇怪怪的问题-Druid+Oracle连接超时关闭问题
SpringBoot+Druid+Oracle连接超时关闭问题
2293 0
|
10月前
|
人工智能 并行计算 Linux
《C++与 CUDA:开启人工智能算法加速之旅》
在AI快速发展的今天,计算效率成为关键挑战。CUDA作为英伟达推出的高性能并行计算平台,极大提升了AI算法的处理速度。本文详细介绍如何在C++环境中安装配置CUDA库,包括系统要求、安装步骤、验证方法及优化技巧,助力开发者高效利用GPU加速AI应用,开启算法加速的新篇章。
226 25
|
人工智能 算法 安全
人工智能伦理:技术发展与道德责任的交汇点
在人工智能(AI)技术的迅猛发展中,伦理问题逐渐成为一个不容忽视的重要议题。本文深入探讨了AI技术在带来便利和效率的同时,可能引发的伦理挑战,包括数据隐私、算法偏见、自动化失业和机器人权利等问题。通过分析当前的研究动态和案例,文章旨在揭示AI伦理问题的复杂性和多维性,并呼吁建立全面的伦理框架以指导AI技术的发展和应用。
481 15
|
Oracle 关系型数据库 数据库
docker下安装oracle11g(一次安装成功)
docker下安装oracle11g(一次安装成功)
1300 0
|
jenkins Shell 持续交付
Jenkins job 创建、参数化、定时构建及时区偏差问题
在Jenkins中创建新任务,步骤:设置任务名,选择自由风格,输入描述并配置丢弃旧构建的规则(保留最近3次)。接着,参数化构建过程,添加字符参数并设定其名称、默认值和描述。在构建触发器中,设定定时构建为每2分钟一次(H2/ * * * *)。在构建阶段执行Shell命令,打印参数param。构建结果显示每2分钟执行一次,保留最近3次构建,参数默认值为Jenkins。定时构建的cron语法详解,并给出常见定时构建示例。当遇到Jenkins显示时间与系统时间不一致(UTC时间差8小时)的问题,解决办法是在Docker启动时添加参数调整时区至上海,确认时区修改成功后,构建历史时间将与系统时间同步。
|
前端开发 Java API
阿里云百炼模型入门篇-大语言模型
本文主要介绍如何快速的通过阿里云百炼,带你如何快速入门通义千问系列大语言模型。
2302 6
|
JSON JavaScript API
wangEditor 富文本详解(下)
wangEditor 富文本详解(下)
1309 0
|
存储 设计模式 网络协议
Netty网络框架(一)
Netty网络框架
936 1