Golang Compare And Swap算法详细介绍
作者:~庞贝 发布时间:2024-02-19 16:08:02
CAS算法(compare and swap)
CAS算法涉及到三个操作数
需要读写的内存值V
进行比较的值A
拟写入的新值B
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
CAS算法(Compare And Swap),是原子操作的一种, CAS算法是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。
该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。
Go中的CAS操作是借用了CPU提供的原子性指令来实现。CAS操作修改共享变量时候不需要对共享变量加锁,而是通过类似乐观锁的方式进行检查,本质还是不断的占用CPU 资源换取加锁带来的开销(比如上下文切换开销)。
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var (
counter int32 //计数器
wg sync.WaitGroup //信号量
)
func main() {
threadNum := 5
wg.Add(threadNum)
for i := 0; i < threadNum; i++ {
go incCounter(i)
}
wg.Wait()
}
func incCounter(index int) {
defer wg.Done()
spinNum := 0
for {
// 原子操作
old := counter
ok := atomic.CompareAndSwapInt32(&counter, old, old+1)
if ok {
break
} else {
spinNum++
}
}
fmt.Printf("thread,%d,spinnum,%d\n", index, spinNum)
}
当主函数main首先创建了5个信号量,然后开启五个线程执行incCounter方法,incCounter内部执行, 使用cas操作递增counter的值,atomic.CompareAndSwapInt32
具有三个参数,第一个是变量的地址,第二个是变量当前值,第三个是要修改变量为多少,该函数如果发现传递的old值等于当前变量的值,则使用第三个变量替换变量的值并返回true,否则返回false。
这里之所以使用无限循环是因为在高并发下每个线程执行CAS并不是每次都成功,失败了的线程需要重写获取变量当前的值,然后重新执行CAS操作。读者可以把线程数改为10000或者更多就会发现输出thread,5329,spinnum,1
其中这个1就说明该线程尝试了两个CAS操作,第二次才成功。
因此呢, go中CAS操作可以有效的减少使用锁所带来的开销,但是需要注意在高并发下这是使用cpu资源做交换的。
CAS是如何运行的
我们有两个goroutineA和goroutineB,接下来我们简称 A 和 B, 共享资源称为C
A 和 B 均保存 C 当前的值
A 尝试使用CAS(56,53)更新C的值
C目前为56,可以更新,然后更新成功
B尝试使用CAS(56,53)更新C的值
C已经为53,更新失败
Go中的CAS源码
// CompareAndSwapUint32 executes the compare-and-swap operation for a uint32 value.
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
实际代码文件在
Go / src / runtime / internal / atomic / asm_amd.s文件中
TEXT runtime∕internal∕atomic·Cas64(SB), NOSPLIT, $0-25
MOVQ ptr+0(FP), BX
MOVQ old+8(FP), AX
MOVQ new+16(FP), CX
LOCK
CMPXCHGQ CX, 0(BX)
SETEQ ret+24(FP)
RET
其中我们可以看作
lock(一个命令前缀,在这里用于CMPXCHGQ)可以锁住总线保证多次内存操作的原子性。
然后执行CMPXCHGQ
cmpxchg %cx, %bx;如果AX与BX相等,则CX送BX且ZF置1;否则BX送CX,且ZF清0
拿AX(old) 与 BX(共享数据ptr) 做对比。
相等,则修改BX(共享数据ptr),状态码ZX设置为 1 。
不相等,则将CX(new)置为目前BX(共享数据ptr)的值, 状态码ZX设置为 0
CAS的缺陷
CAS在共享资源竞争比较激烈的时候,每个goroutine会容易处于自旋状态,影响效率,在竞争激烈的时候推荐使用锁。
无法解决ABA问题
ABA问题是无锁结构实现中常见的一种问题,可基本表述为:
进程P1读取了一个数值A
P1被挂起(时间片耗尽、中断等),进程P2开始执行
P2修改数值A为数值B,然后又修改回A
P1被唤醒,比较后发现数值A没有变化,程序继续执行。
来源:https://blog.csdn.net/qq_53267860/article/details/127159500


猜你喜欢
- 本文研究的主要是Python编程argparse的相关内容,具体介绍如下。#aaa.py#version 3.5import os &nbs
- 如果要在某个数组中删除一个元素,可以直接用的unset,但今天看到的东西却让我大吃一惊<?php$arr = array('a
- 复习回顾我们已经对Python内置模块-time中知道时间格式目前有三种。时间戳结构化时间字符串时间本期,我们将继续深入对time模块中所涉
- 问题背景:这个问题是在爬取某夕夕商城遇到的问题,原本的方案是用selenium + chromedriver + mitmproxy开心的刷
- 大家好,今天分享一个非常有趣的 Python 教程,如何美化一个 matplotlib 折线图,喜欢记得收藏、关注、点赞。1. 导入包imp
- vue 百度地图 + 定位 前提需要自己有百度的密钥,如没有可以去百度地图申请一、在主目录下的index.html引入js,例如:
- 刚才好无聊,突然想起来之前做一个课表的点子,于是百度了起来。刚开始,我是这样想的:在写微信墙的时候,用到了urllib2【两行代码抓网页】,
- OpenCV 是一个流行的开源计算机视觉库,可用于不同的编程语言,例如 Python、C++ 和 JavaScript。它提供了一套丰富的工
- 在上一期python numpy 模块中对概述介绍了numpy 模块安装、使用方法、特点等入门知识。numpy 模块是一个开源的第三方Pyt
- Flask 框架中如果想要实现WebSocket功能有许多种方式,运用SocketIO库来实现无疑是最简单的一种方式,Flask中封装了一个
- 在视图中也有笨方法可以从数据库中获取数据。 很简单: 用现有的任何 Python 类库执行一条 SQL 查询并对结果进行一些处理。在本例的视
- 现在大家学习python掌握内容了解太多太多,但是最重要的不是掌握了解算法的使用,而是了解算法原理远比使用算法命令更重要,现在大家了解算法应
- http://swik.net/Ajax/Ajax+Mistakes在某网站瞎逛时,发现这个链接,进去逛了逛,觉得很有意思,大家也可以去看看
- 本文实例讲述了Python 网络编程之UDP发送接收数据功能。分享给大家供大家参考,具体如下:demo.py(UDP发送数据):import
- 目录获取二维码部分获取关注状态值解析微信服务器报文大致思路:调用微信带参数二维码接口生成二维码,前端显示二维码同时于服务器进行长链接通信,监
- 尝试用python写文件,但是无法写入文件,文件内容为空。原代码片段如下,poem = "This is a poem"
- 拉取网易蜂巢的mysql-server:5.6docker pull hub.c.163.com/nce2/mysql:5.6创建mysql
- pycharm创建sql文件及模板创建模板pycharm默认新建文件选项中没有sql文件,每次通过文件末尾添加.sql识别文件格式很麻烦。可
- Java 正则表达式正则表达式定义了字符串的模式。正则表达式可以用来搜索、编辑或处理文本。正则表达式并不仅限于某一种语言,但是在每种语言中有
- 1. 安装依赖将PyTorch模型转换为ONNX格式可以使它在其他框架中使用,如TensorFlow、Caffe2和MXNet首先安装以下必