本文我们详细聊一下Go
语言的原子操作的用法,啥是原子操作呢?顾名思义,原子操作就是具备原子性的操作... 是不是感觉说了跟没说一样,原子性的解释如下:
一个或者多个操作在 CPU 执行的过程中不被中断的特性,称为原子性(atomicity) 。这些操作对外表现成一个不可分割的整体,他们要么都执行,要么都不执行,外界不会看到他们只执行到一半的状态。
CPU
执行一系列操作时不可能不发生中断,但如果我们在执行多个操作时,能让他们的中间状态对外不可见,那我们就可以宣称他们拥有了"不可分割”的原子性。
类似的解释我们在数据库事务的ACID
概念里也听过。
Go
语言通过内置包sync/atomic
提供了对原子操作的支持,其提供的原子操作有以下几大类:
AddXXXType
,保证对操作数进行原子的增减,支持的类型为int32
、int64
、uint32
、uint64
、uintptr
,使用时以实际类型替换前面我说的XXXType
就是对应的操作方法。LoadXXXType
,支持的类型除了基础类型外还支持Pointer
,也就是支持载入任何类型的指针。Store
开头,支持的类型跟载入操作支持的那些一样。CAS
(Compare And Swap),像Go
的很多并发原语实现就是依赖的CAS
操作,同样是支持上面列的那些类型。平日里,在并发编程里,Go语言sync
包里的同步原语Mutex
是我们经常用来保证并发安全的,那么他跟atomic
包里的这些操作有啥区别呢?在我看来他们在使用目的和底层实现上都不一样:
Mutex
由操作系统的调度器实现,而atomic
包中的原子操作则由底层硬件指令直接提供支持,这些指令在执行的过程中是不允许中断的,因此原子操作可以在lock-free
的情况下保证并发安全,并且它的性能也能做到随CPU
个数的增多而线性扩展。对于一个变量更新的保护,原子操作通常会更有效率,并且更能利用计算机多核的优势。
比如下面这个,使用互斥锁的并发计数器程序:
func mutexAdd() {
var a int32 = 0
var wg sync.WaitGroup
var mu sync.Mutex
start := time.Now()
for i := 0; i < 100000000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
a += 1
mu.Unlock()
}()
}
wg.Wait()
timeSpends := time.Now().Sub(start).Nanoseconds()
fmt.Printf("use mutex a is %d, spend time: %v\n", a, timeSpends)
}
把Mutex
改成用方法atomic.AddInt32(&a, 1)
调用,在不加锁的情况下仍然能确保对变量递增的并发安全。
func AtomicAdd() {
var a int32 = 0
var wg sync.WaitGroup
start := time.Now()
for i := 0; i < 1000000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddInt32(&a, 1)
}()
}
wg.Wait()
timeSpends := time.Now().Sub(start).Nanoseconds()
fmt.Printf("use atomic a is %d, spend time: %v\n", atomic.LoadInt32(&a), timeSpends)
}
可以在本
可以在本地运行以上这两段代码,可以观察到计数器的结果都最后都是1000000
,都是线程安全的。
需要注意的是,所有原子操作方法的被操作数形参必须是指针类型,通过指针变量可以获取被操作数在内存中的地址,从而施加特殊的CPU指令,确保同一时间只有一个goroutine能够进行操作。
上面的例子除了增加操作外我们还演示了载入操作,接下来我们来看一下CAS
操作。
该操作简称CAS
(Compare And Swap)。这类操作的前缀为 CompareAndSwap
:
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
该操作在进行交换前首先确保被操作数的值未被更改,即仍然保存着参数 old
所记录的值,满足此前提条件下才进行交换操作。CAS
的做法类似操作数据库时常见的乐观锁机制。
需要注意的是,当有大量的goroutine 对变量进行读写操作时,可能导致CAS
操作无法成功,这时可以利用for
循环多次尝试。
上面我只列出了比较典型的int32
和unsafe.Pointer
类型的CAS
方法,主要是想说除了读数值类型进行比较交换,还支持对指针进行比较交换。
unsafe.Pointer提供了绕过Go语言指针类型限制的方法,unsafe指的并不是说不安全,而是说官方并不保证向后兼容。
// 定义一个struct类型P
type P struct{ x, y, z int }
// 执行类型P的指针
var pP *P
func main() {
// 定义一个执行unsafe.Pointer值的指针变量
var unsafe1 = (*unsafe.Pointer)(unsafe.Pointer(&pP))
// Old pointer
var sy P
// 为了演示效果先将unsafe1设置成Old Pointer
px := atomic.SwapPointer(
unsafe1, unsafe.Pointer(&sy))
// 执行CAS操作,交换成功,结果返回true
y := atomic.CompareAndSwapPointer(
unsafe1, unsafe.Pointer(&sy), px)
fmt.Println(y)
}
上面的示例并不是在并发环境下进行的CAS
,只是为了演示效果,先把被操作数设置成了Old Pointer
。
其实Mutex
的底层实现也是依赖原子操作中的CAS
实现的,原子操作的atomic
包相当于是sync
包里的那些同步原语的实现依赖。
比如互斥锁Mutex
的结构里有一个state
字段,其是表示锁状态的状态位。
type Mutex struct {
state int32
sema uint32
}
为了方便理解,我们在这里将它的状态定义为0和1,0代表目前该锁空闲,1代表已被加锁,以下是sync.Mutex
中Lock
方法的部分实现代码。
func (m *Mutex) Lock() {
// Fast path: grab unlocked mutex.
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
// Slow path (outlined so that the fast path can be inlined)
m.lockSlow()
}
在atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked)
中,m.state
代表锁的状态,通过CAS
方法,判断锁此时的状态是否空闲(m.state==0
),是,则对其加锁(mutexLocked
常量的值为1)。
atomic
包里提供了一套Store
开头的方法,用来保证各种类型变量的并发写安全,避免其他操作读到了修改变量过程中的脏数据。
func StoreInt32(addr *int32, val int32)
func StoreInt64(addr *int64, val int64)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
...
这些操作方法的定义与上面介绍的那些操作的方法类似,我就不再演示怎么使用这些方法了。
值得一提的是如果你想要并发安全的设置一个结构体的多个字段,除了把结构体转换为指针,通过StorePointer
设置外,还可以使用atomic
包后来引入的atomic.Value
,它在底层为我们完成了从具体指针类型到unsafe.Pointer
之间的转换。
有了atomic.Value
后,它使得我们可以不依赖于不保证兼容性的unsafe.Pointer
类型,同时又能将任意数据类型的读写操作封装成原子性操作(中间状态对外不可见)。
atomic.Value
类型对外暴露了两个方法:
v.Store(c)
- 写操作,将原始的变量c
存放到一个atomic.Value
类型的v
里。c := v.Load()
- 读操作,从线程安全的v
中读取上一步存放的内容。1.17 版本我看还增加了Swap
和CompareAndSwap
方法。
简洁的接口使得它的使用也很简单,只需将需要做并发保护的变量读取和赋值操作用Load()
和Store()
代替就行了。
由于Load()
返回的是一个interface{}
类型,所以在使用前我们记得要先转换成具体类型的值,再使用。下面是一个简单的例子演示atomic.Value
的用法。
type Rectangle struct {
length int
width int
}
var rect atomic.Value
func update(width, length int) {
rectLocal := new(Rectangle)
rectLocal.width = width
rectLocal.length = length
rect.Store(rectLocal)
}
func main() {
wg := sync.WaitGroup{}
wg.Add(10)
// 10 个协程并发更新
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
update(i, i+5)
}()
}
wg.Wait()
_r := rect.Load().(*Rectangle)
fmt.Printf("rect.width=%d\nrect.length=%d\n", _r.width, _r.length)
}
你也可以试试,不用atomic.Value
,直接给Rectange
类型的指针变量赋值,看看在并发条件下,两个字段的值是不是能跟预期的一样变成10和15。
本文详细介绍了Go语言原子操作atomic
包中会被高频使用的操作的使用场景和用法,当然我并没有罗列atomic
包里所有操作的用法,主要是考虑到有的用到的地方实在不多,或者是已经被更好的方式替代,还有就是觉得确实没必要,看完本文的内容相信你已经完全具备自行探索atomic
包的能力了。
再强调一遍,原子操作由底层硬件支持,而锁则由操作系统的调度器实现。锁应当用来保护一段逻辑,对于一个变量更新的保护,原子操作通常会更有效率,并且更能利用计算机多核的优势,如果要更新的是一个复合对象,则应当使用atomic.Value
封装好的实现。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/xgIJnw0yEGfIIMiARtAGRg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。