Golang sync包提供了基础的异步操作方法,包括互斥锁Mutex,执行一次Once和并发等待组WaitGroup。
设计模式(Design pattern),提供了在软件开发过程中面临的一些问题的最佳解决方案。主要分创建型模式、结构型模式和行为型模式。
首先来看创建型模式(Creational Patterns),它提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。单例模式属于创建型模式,单例模式,顾名思义就是只有一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式又分为饿汉方式和懒汉方式。
对于golang,饿汉方式指全局的单例实例在包被加载时创建,而懒汉方式指全局的单例实例在第一次被使用时创建。除了自己手写饿汉方式和懒汉方式,在 Go 开发中,还有一种更优雅的实现方式(使用sync包的once.Do)
sync.Once 指的是只执行一次的对象实现,常用来控制某些函数只能被调用一次。sync.Once的使用场景例如单例模式、系统初始化。例如并发情况下多次调用channel的close会导致panic,解决这个问题我们可以使用sync.Once来保证close只会被执行一次。sync.Once的结构如下所示,只有一个函数。使用变量done来记录函数的执行状态,使用sync.Mutex和sync.atomic来保证线程安全的读取done。
type Once struct {
m Mutex #互斥锁
done uint32 #执行状态
}
func (o *Once) Do(f func())
举个例子,1000个并发协程情况下只有一个协程会执行到fmt.Printf,多次执行的情况下输出的内容还不一样,因为这取决于哪个协程先调用到该匿名函数。
func main() {
once := &sync.Once{}
for i := 0; i < 1000; i++ {
go func(idx int) {
once.Do(func() {
time.Sleep(1 * time.Second)
fmt.Printf("hello world index: %d", idx)
})
}(i)
}
time.Sleep(5 * time.Second)
}
demo:使用sync包的once.Do,实现单例模式
package singleton
import (
"sync"
)
type singleton struct {
}
var ins *singleton
var once sync.Once
func GetInsOr() *singleton {
once.Do(func() {
ins = &singleton{}
})
return ins
}
使用noce.Do调用时方法内执行代码只执行一次。输出结果:
Create Obj
6c7df8
6c7df8
6c7df8
6c7df8
6c7df8
6c7df8
6c7df8
6c7df8
在读多写少的环境中,可以优先使用读写互斥锁(sync.RWMutex),它比互斥锁更加高效。sync 包中的 RWMutex 提供了读写互斥锁的封装。读写锁分为:读锁和写锁:
通过设置写锁,同样可以实现数据的一致性:
package main
import ("fmt"
"sync"
)
var (
count int
rwLock sync.RWMutex
)
func main() {
for i := 0; i < 2; i++ {
go func() {
for i := 1000000; i > 0; i-- {
rwLock.Lock()
count ++
rwLock.Unlock()
}
fmt.Println(count)
}()
}
fmt.Scanf("\n") //等待子线程全部结束
}
1968637
2000000