Golang 状态的原子操作

Golang 状态的原子操作

在处理状态的切换处理时为了多线程安全一般会用原子操作实现,这里记录下使用的情况。

1. 在只处理状态量时原子操作对比锁操作

  1. 只有单个状态量,原子操作比锁更简洁高效
  2. 使用原子操作也不会影响到其他线程
  3. 原子操作限制只有一些基本的加减、比较交换,不过针对状态型变量已经够了

2. Golang 原子操作

2.1 CompareAndSwap 方法 ChatGpt 讲解

CompareAndSwap(CAS)是一个常见的原子操作,常用于实现无锁(lock-free)数据结构。CAS 通过比较当前值和预期值,来决定是否更新某个变量。这个操作通常涉及三个参数:

CompareAndSwap(address, expected, new)

参数解释
address:要操作的内存地址,指向需要修改的值。
expected(old):期望的旧值,表示你“希望”当前值是的值。
new:想要设置的新值。
工作原理
CAS 读取 address 的当前值。
如果当前值与 expected 相等,则将其更新为 new,返回 true(表示成功)。
如果当前值与 expected 不相等,表示在这个操作期间其他线程修改了该值,CAS 操作会失败,返回 false(表示失败)。

示例(伪代码)

// 期望值 old 是 100
// 如果当前值是 100,就更新为 200
if (CompareAndSwap(address, 100, 200)) {
    // 更新成功
    System.out.println("Value updated!");
} else {
    // 更新失败(说明其他线程已修改)
    System.out.println("Update failed!");
}

3. 原子实操分析

情景 1 Tcp 连接状态管理

func (t *TcpConn) Close() {
    if t.isClosed.CompareAndSwap(false, true) { // CAS 检查并设置关闭状态
       t.cancel() // 取消关联的上下文(context)
       t.Conn.Close() // 真正关闭 TCP 连接
       go t.OnDisconnect.RiseEvent(nil) // 异步触发断开事件
    }
}

这里 Close 连接的时候只希望执行关闭操作一次,所以拿 IsClosed 【atomic.bool】作为该状态量,Close 时比对是否连接已经关闭过了(==false),如果没有关闭过(!=false),才会(Swap 该变量到 True)即执行触发条件执行一系列动作。可以设想下多线程执行 Close 方法情况,在 CompareAndSwap 时都会先去比对 false,一个执行成功,其他就不会成功啦!

对比下原写法

func (t *TcpConn) Close() {
    isClosed := t.isClosed.Load()
    if t.isClosed.CompareAndSwap(isClosed, true) {
        //一系列动作
    } 
}

这里先读取了下 isClosed 状态,然后再次进行 CAS 操作,这个也是线程安全的,对比上面写法可以看出不够简洁

情景 2 设备在线离线状态管理

func (d *Device) SetOnline(value int32) {
    old := atomic.SwapInt32(&d.IsOnline, value)
    if value != old {
       db.sqlUpdateDeviceOnOffline(d.Sn, value)
       //进行上线告警
       //存储下线位置
       ...
    }
}

这里得到了最新在线离线状态输入到 SetOnline 后进行判断最新在线状态是否和当前状态不一致,即设备切到了上线状态、设备切换到了离线状态,如果发生了状态切换则会进行数据库状态更新、上下线告警等等

在这个场景下使用 isOnline 为【int32】,atomic.SwapInt32 为原子操作修改在线状态为最新状态,得到 old 状态,比对最新状态和 old 状态,如果不等即发生了状态变化

其他写法

  1. 使用 atomic.LoadInt32 + CompareAndSwapInt32
func (d *Device) SetOnline(value int32) {
    old := atomic.LoadInt32(&d.IsOnline) // 用原子操作读取当前值
    if atomic.CompareAndSwapInt32(&d.IsOnline, old, value) {
        log.Printf("Device state changed: %d -> %d", old, value)
        //...........
    }
}

这个也是线程安全的

  1. 错误写法
func (d *Device) SetOnline(value int32) {
    if atomic.CompareAndSwapInt32(&d.IsOnline, d.IsOnline, value) {//d.IsOnline为普通读取
        // 状态更新成功
    }
}

func (d *Device) GetOnline(){
    return d.IsOnline //d.IsOnline为普通读取
}

d.IsOnline 读取非原子操作,使用非原子读和原子操作写在并发情况下可能会造成线程安全问题,即

线程 A 开始原子修改 IsOnline 的值。

线程 B 在 SetOnline(1) 执行到一半时,读取 IsOnline 的值。

由于 d.IsOnline 为直接读取而不是通过原子操作,可能读取到部分修改的值不一致的状态

情景 3 客户端管理

func (n *Hub) AddClient(new *Client) {
    logger.Info("hub新增加客户端", zap.String("clientId", new.ClientId))
    if pre, ok := n.clients.Swap(new.ClientId, new); ok {
       logger.Error(fmt.Sprintf("clientId【%v】已连接,关闭旧连接", new.ClientId))
       pre.(*Client).Dispose()
    }
    new.OnDispose.AddEventListener(func(data interface{}) {
       if n.clients.CompareAndDelete(new.ClientId, new) {
          logger.Info("hub移除客户端", zap.String("clientId", new.ClientId))
   }
})

clients 为【sync.map】

if pre, ok := n.clients.Swap(new.ClientId, new); ok {

这里使用 Swap 操作得到和新 client 冲突的 client,如果存在则进行释放

n.clients.CompareAndDelete(new.ClientId, new)

在触发 Client 释放事件时,这里使用 CompareAndDelete 操作比对存储的 Clients 里是否还是自己,如果是自己则进行移除操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值