0%

golang多线程编发之Sync包

Golang为了能够充分利用多核CPU的性能,在调度系统中使用了多线程,也就是Goroutine是跑在多个线程中,当多个Goroutine访问同一个资源是会出现c/cpp/java中并发问题; Sync包就是来解决这些并发问题

1. Sync中锁

在sync包中有两种锁,互斥锁sync.Mutex, 读写锁sync.RWMutex, 遗憾的是gloang官方包并没有自旋锁,当然可以使用atomic来模拟自旋锁

1
2
3
4
5
6
7
8
9
//互斥锁
func (m *Mutex) Lock()
func (m *Mutex) Unlock()

//读写锁
func (rw *RWMutex) RLock()
func (rw *RWMutex) RUnlock()
func (rw *RWMutex) Lock()
func (rw *RWMutex) Unlock()

2. Sync.WaitGroup

sync包中WaitGroup 用于等待一组 goroutine 结束,用法很简单。它有三个方法:

1
2
3
func (wg *WaitGroup) Add(delta int) //增加waitgoroutine个数
func (wg *WaitGroup) Done() //减少waitgoroutine个数,被等待进程结束调用
func (wg *WaitGroup) Wait() //等待结束,计数为0是,唤醒

注意,wg.Add() 方法一定要在goroutine开始前执行

3. Sync.Cond

Cond 实现一个条件变量,即等待或宣布事件发生的 goroutines 的会合点

1
2
3
4
func NewCond(l Locker) *Cond //初始化Cond需要一个Mutex,当然可以自定义
func (c *Cond) Broadcast() //唤醒所有Cond.Wait
func (c *Cond) Signal() //唤醒一个Cond.Wait
func (c *Cond) Wait() //等待

sync.Cond使用比较复杂,这里给出一个使用例子

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
package main

import (
"fmt"
"sync"
"time"
)

func main() {
//初始化Cond, 这里需要传入Mutex指针
var cond = sync.NewCond(new(sync.Mutex))
for i := 0; i < 5; i++ {
go func(x int) {
cond.L.Lock()
defer cond.L.Unlock()
cond.Wait()
fmt.Printf("id:%d, time:%d\n", x, time.Now().Unix())
}(i)
}

time.Sleep(time.Second) //等待所有Gorontine进入等待状态
cond.Signal() //唤醒一个Gorontine
time.Sleep(2 * time.Second)
cond.Signal() //再次唤醒一个Gorontine
time.Sleep(2 * time.Second)
cond.Broadcast() //唤醒剩下所有Gorontine
time.Sleep(time.Second)
}

运行结果如下

4. Sync.Pool

Golang中Sync.Pool可以作为对象池使用,且是线程安全的

1
2
3
4
5
6
7
8
type Demo struct{}
pool := sync.Pool{
New: func() interface{} {
return &Demo{}
},
}
func (p *Pool) Get() interface{} //获取对象,池中为空时,调用New方法
func (p *Pool) Put(x interface{}) //释放对象

5. Sync.Once

Golang中经常有场景需要初始化全局变量,但有可能存在并发问题,这里就使用到了sync.Once

1
func (o *Once) Do(f func()) //执行函数,生命周期内只会执行一次