【Go】原子操作atomic.Value的使用

概述

Go的sync/atomic包提供了原子操作,支持的数据类型包括:

int32, int64, uint32, uint64, uintptr, unsafe.Pointer

1

若需要扩大原子操作的适用范围,可以使用atomic包中的Value。利用它可以实现对任意值进行原子得存储与加载。

使用注意点

atomic.Value只有两个指针方法:Store、Load。使用时需要遵循两个原则:1.不能存储nil;2.存储第一个值后,就只能存储这个类型的值。

看一下atom.Value的实现可以发现一个内部的结构ifaceWords,使用unsafe.Pointer存储数据类型及内容得指针

type ifaceWords struct {

typ unsafe.Pointer

data unsafe.Pointer

}

1

2

3

4

在使用Store进行存储时,首先判断待存储值是否为nil,若为nil会直接panic。之后会读取typ,若为nil则会将待存储值得类型、值的指针分别对typ、data进行赋值;若不为nil,则会判断待存储值的类型是否与既有的typ一致,不一致也会引起panic,一致的话则是将新值的指针赋予data。

为了防止在使用是意外出现panic,所以可以考虑在外部先进行合法性校验。

引用类型带来的坑点

因为atom.Value内部实际上维护的是存储值的指针,而这个指针因为不对外暴露,所以认为是并发安全的。然而如果尝试用它来存储引用类型,维护的就是这个引用类型的指针,则不能保证实际的数据是并发安全的。举个例子:

uint32是值类型,切片[]uint32是引用类型,我们使用这样两个函数来尝试修改值。

//值类型

func atomic_value(a uint32) {

var v atomic.Value

v.Store(a)

a = 666

fmt.Println(a)

fmt.Println(v.Load())

}

// 引用类型

func atomic_slice(s []uint32) {

var v atomic.Value

v.Store(s)

s[0] = 666

fmt.Println(s)

fmt.Println(v.Load())

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

然后我们分别调用这两个函数

func TestAtomValue(t *testing.T) {

atomic_value(1)

atomic_slice([]uint32{1,2,3})

}

1

2

3

4

最后它的输出是这样的:

可以看到我们使用Store存入uint32的值a后,我们无论怎么在外部修改a,使用Load都可以获取到我们Store的值。

然而我们若使用Store存入引用类型的切片,我们在外部修改值,Load出来的值也会收到影响。这是因为对于一个引用类型,我们实际上只是Store了一个指针,只是对一个指针的原子操作,而这个指针实际指向的地址的值,并不在atomic.Value的维护下,所以并不是并发安全的。

总结

1.atomic.Value可以实现对自定义类型的原子操作

2.不能存入nil

3.对于同一个atomic.Value不能存入类型不同的值

4.最好不要使用atomic.Value存储引用类型的值,可能导致数据不是并发安全的

————————————————

版权声明:本文为CSDN博主「雨儿酱在鹿上」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/q895431756/article/details/111063656