golang 数据结构 2

  • channel
  • atomic
  • sync.Mutex
  • sync.WaitGroup
  • sync.Pool
  • sync.Map

channel

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var ch chan int 
ch := make(chan int)
ch := make(chan int, 2) // 带缓存 chan

ch <- value // 给 channel 发送值
value := <- ch // 从 channel 接收值

// for range channel
for value := range ch {
}

// 函数中使用
func run(ch chan int){}
func run(ch <-chan int) {} // 指定是一个只能接收 channel
func run(ch chan<- int) {} // 指定是一个只能发送 channel

close(ch) // 关闭 channel,关闭后不能再往 channel 发送消息

atomic

https://golang.org/pkg/sync/atomic/

各种原子操作

1
2
3
4
5
6
7
8
9
10
11
var data int32
data = atomic.AddInt32(data, 1) // int类型原子操作,还有方法:AddInt64,AddUint32 etc.

// 支持任何类型
var value atomic.Value

value.Store(data) // 存储
data := value.Load().(int32) // 获取

// 进行 compare-and-swap(CAS) 操作
swaped := atomic.CompareAndSwapInt32(&data, data, 2)

atomic.Value 可以使用在项目中存储 config。

比较并交换 CAS

sync.Mutex

[Go 教程系列笔记] Mutex(互斥锁)

1
2
3
4
5
6
7
8
9
// 互斥锁一般配合 struct 一起用
clazz := struct {
data int
sync.Mutex
}

clazz.Lock() // 申请锁
clazz.data++
clazz.Unlock() // 释放锁

sync.RWMutex

读写锁是可以获得任意多的读锁和一个写锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 互斥锁一般配合 struct 一起用
clazz := struct {
data int
sync.Mutex
}

clazz.RLock() // 申请读锁
fmt.Println(clazz.data)
clazz.RUnlock() // 释放读锁

clazz.Lock() // 申请写锁
clazz.data++
clazz.Unlock() // 释放写锁

在获取写锁时,必须读锁和写锁都必须释放后才能获得。不然会进入阻塞。

sync.Pool

Pool 是一组可以单独保存和获取的临时对象。

任何存储在 Pool 的元素可能在任何时候被删除而不会通知。

Pool 在多个 goroutines 同时使用是线程安全的。

Pool 目的是缓存已分配但未使用的元素。以减少内存分配,缓解 GC 的压力。

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}

func Log(w io.Writer, key, val string) {
b := bufPool.Get().(*bytes.Buffer)
b.Reset() // Pool 获取的元素都需要做初始化

b.WriteString(time.Now().Format(time.RFC3339))
b.WriteByte(' ')
b.WriteString(key)
b.WriteByte('=')
b.WriteString(val)
w.Write(b.Bytes())
bufPool.Put(b) // 放回 Pool
}

// Pool 定义
type Pool struct {
// New 设置一个函数用来创建元素
New func() interface{}
}

// Pool 方法
func (p *Pool) Get() interface{} // 获取元素
func (p *Pool) Put(x interface{}) // 放回元素

sync.Pool 的问题是,Pool 中的对象会随时被 GC 清理掉,这使得 sync.Pool 只适合做简单的对象池,而不适合做连接池(http.Client,redis.Client 这类连接对象)。

https://studygolang.com/articles/16282

sync.Map

https://golang.org/pkg/sync/#Map

使用 sync.Map 的原因是 golang 的 map 的读写不是并发安全的。

Map 是像 map[interface{}]interface{} 但在多个 goroutines 使用是并发安全。

Map 类型优化的两个点:

  • (1) 当一个键值对只写一次,但读很多次,就像在只增长的缓存中一样。
  • (2) 当多个 goroutines 读取、写入和覆盖不相交的键值对时。

当是这两类情况下,比起 map 搭配 Mutex 或 RWMutex,使用 Map 可以显著减少锁的争用情况。

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var hash sync.Map

hash.Store("name", "leon") // 存储 key, value
value, ok := hash.Load("name") // 读取

actual, loaded := hash.LoadOrStore("name", "leonXu") // 读取或更新

hash.Delete("name") // 删除

final = make(map[interface{}]interface{})
// 循环 Map
hash.Range(func(k, v interface{}) bool {
final[k] = v
return true
})

Go 1.9 sync.Map揭秘

Ref