sync包
sync包
一、sync还是channel
Go语言提倡“不要通过共享内存来通信,而应该通过通信来共享内存”。正如在前文阐述的那样,建议大家优先使用CSP并发模型进行并发程序设计。但是在下面一些场景下,我们依然需要sync包提供的低级同步原语。
(1)需要高性能的临界区同步机制场景。
在Go中,channel属于高级同步原语,其实现是建构在低级同步原语之上的。因此,channel自身的性能与低级同步原语相比要略微逊色。
(2)不想转移结构体对象所有权,但又要保证结构体内部状态数据的同步访问的场景。
基于channel的并发设计的一个特点是,在goroutine间通过channel转移数据对象的所有权。只有拥有数据对象所有权(从channel接收到该数据)的goroutine才可以对该数据对象进行状态变更。如果你的设计中没有转移结构体对象所有权,但又要保证结构体内部状态数据能在多个goroutine之间同步访问,那么你可以使用sync包提供的低级同步原语来实现,比如最常用的sync.Mutex。
(3)需要通知具体协程的用channel
二、使用sync包的注意事项------使用sync包中类型时,推荐通过闭包方式或传递类型实例(或包裹该类型的类型实例)的地址或指针的方式进行,这是使用sync包最值得注意的事项。
// $GOROOT/src/sync/mutex.go
type Mutex struct {
state int32
sema uint32
}
// Values containing the types defined in this package should not be copied.
// 不应复制那些包含了此包中类型的值
// $GOROOT/src/sync/mutex.go
// A Mutex must not be copied after first use. (禁止复制首次使用后的Mutex)
// $GOROOT/src/sync/rwmutex.go
// A RWMutex must not be copied after first use.(禁止复制首次使用后的RWMutex)
// $GOROOT/src/sync/cond.go
// A Cond must not be copied after first use.(禁止复制首次使用后的Cond)
...
// chapter6/sources/go-sync-package-2.go
type foo struct {
n int
sync.Mutex
}
func main() {
f := foo{n: 17}
go func(f foo) {
for {
log.Println("g2: try to lock foo...")
f.Lock()
log.Println("g2: lock foo ok")
time.Sleep(3 * time.Second)
f.Unlock()
log.Println("g2: unlock foo ok")
}
}(f)
f.Lock()
log.Println("g1: lock foo ok")
// 在Mutex首次使用后复制其值
go func(f foo) {
for {
log.Println("g3: try to lock foo...")
f.Lock()
log.Println("g3: lock foo ok")
time.Sleep(5 * time.Second)
f.Unlock()
log.Println("g3: unlock foo ok")
}
}(f)
time.Sleep(1000 * time.Second)
f.Unlock()
log.Println("g1: unlock foo ok")
}
$go run go-sync-package-2.go
2020/02/08 21:16:46 g1: lock foo ok
2020/02/08 21:16:46 g2: try to lock foo...
2020/02/08 21:16:46 g2: lock foo ok
2020/02/08 21:16:46 g3: try to lock foo...
2020/02/08 21:16:49 g2: unlock foo ok
2020/02/08 21:16:49 g2: try to lock foo...
2020/02/08 21:16:49 g2: lock foo ok
2020/02/08 21:16:52 g2: unlock foo ok
2020/02/08 21:16:52 g2: try to lock foo...
2020/02/08 21:16:52 g2: lock foo ok
...
//g3阻塞
三、互斥锁还是读写锁
在并发量较小的情况下,互斥锁性能更好。
读写锁适合应用在具有一定并发量且读多写少的场合。
四、条件变量-Cond -- 主协程通知子协程执行
type Cond struct {
L Locker
}
// 创建一个带锁的条件变量,Locker 通常是一个 *Mutex 或 *RWMutex
func NewCond(l Locker) *Cond
// 唤醒所有因等待条件变量 c 阻塞的 goroutine
func (c *Cond) Broadcast()
// 唤醒一个因等待条件变量 c 阻塞的 goroutine
func (c *Cond) Signal()
// 等待 c.L 解锁并挂起 goroutine,在稍后恢复执行后,Wait 返回前锁定 c.L,只有当被 Broadcast 和 Signal 唤醒,Wait 才能返回
func (c *Cond) Wait()
//!!!!!!在调用 Signal,Broadcast 之前,应确保目标 Go 程进入 Wait 阻塞状态!!!!!!
package main
import (
"fmt"
"sync"
"time"
)
var locker = new(sync.Mutex)
var cond = sync.NewCond(locker)
func main() {
for i := 0; i < 10; i++ {
go func(x int) {
cond.L.Lock() //获取锁
defer cond.L.Unlock() //释放锁
cond.Wait() //等待通知,阻塞当前goroutine
fmt.Println(x)
}(i)
}
time.Sleep(time.Second * 1) // 睡眠1秒,使所有goroutine进入 Wait 阻塞状态
fmt.Println("Signal...")
cond.Signal() // 1秒后下发一个通知给已经获取锁的goroutine
time.Sleep(time.Second * 1)
fmt.Println("Signal...")
cond.Signal() // 1秒后下发下一个通知给已经获取锁的goroutine
time.Sleep(time.Second * 1)
cond.Broadcast() // 1秒后下发广播给所有等待的goroutine
fmt.Println("Broadcast...")
time.Sleep(time.Second * 1) // 睡眠1秒,等待所有goroutine执行完毕
}
五、sync.WaitGroup -- 主协程等待子协程先执行
func TestWaitgroup(t *testing.T) {
var wg sync.WaitGroup
// 计数器 +2
wg.Add(2)
go func() {
dosomething()
// 计数器 -1
wg.Done()
}()
go func() {
dosomething()
// 计数器 -1
wg.Done()
}()
// 阻塞。计数器为 0 的时候,Wait 返回
wg.Wait()
}
六、sync.Once--实现单例模式
once.Do会等待f执行完毕后才返回,这期间其他执行once.Do函数的goroutine(如上面运行结果中的goroutine 2~5)将会阻塞等待;Do函数返回后,后续的goroutine再执行Do函数将不再执行f并立即返回(如上面运行结果中的goroutine 0);即便在函数f中出现panic,sync.Once原语也会认为once.Do执行完毕,后续对once.Do的调用将不再执行f。
七、使用sync.Pool减轻垃圾回收压力-----数据对象缓存池
//它是goroutine并发安全的,可以被多个goroutine同时使用;放入该缓存池中的数据对象的生命是暂时的,随时都可能被垃圾回收掉;缓存池中的数据对象是可以重复利用的,这样可以在一定程度上降低数据对象重新分配的频度,减轻GC的压力;sync.Pool为每个P(goroutine调度模型中的P)单独建立一个local缓存池,进一步降低高并发下对锁的争抢。
//sync.Pool的一个典型应用就是建立像bytes.Buffer这样类型的临时缓存对象池
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
// chapter6/sources/go-sync-package-7_test.go
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func writeBufFromPool(data string) {
b := bufPool.Get().(*bytes.Buffer)
b.Reset()
b.WriteString(data)
bufPool.Put(b)
}
func writeBufFromNew(data string) *bytes.Buffer {
b := new(bytes.Buffer)
b.WriteString(data)
return b
}
//(1)限制要放回缓存池中的数据对象大小
//(2)建立多级缓存池
八、sync.map
//sync.map并发安全
var m sync.Map
// 1. 写
m.Store("k1", 1)
m.Store("k2", 2)
// 2. 读
i, _ := m.Load("k1")
fmt.Println(i.(int))
// 3. 遍历
m.Range(func(key, value interface{}) bool {
k := key.(string)
v := value.(int)
fmt.Println(k, v)
return true
})
// 4. 删除
m.Delete("k1")
i, ok := m.Load("k1")
fmt.Println(i, ok)
// 5. 读取,不存在则写入
m.LoadOrStore("k3", 3)
i, _ = m.Load("k3")
fmt.Println(i)

浙公网安备 33010602011771号