互斥锁
使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待锁;当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的。
package main
import (
"fmt"
"sync"
)
var global int64
var wg sync.WaitGroup
var lock sync.Mutex
func add() {
defer wg.Done()
for i := 0; i < 5000; i++ {
lock.Lock() // 加锁
global++
lock.Unlock() // 解锁
}
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(global)
}
读写锁
对于读多写少的场景下使用读写锁性能会比互斥锁好。
写锁优先级高,写锁独占,读锁共享。
package main
import (
"fmt"
"sync"
"time"
)
var (
wg sync.WaitGroup
lock sync.Mutex
rwlock sync.RWMutex
)
func write() {
// lock.Lock() // 加互斥锁
rwlock.Lock() // 加写锁
time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
rwlock.Unlock() // 解写锁
// lock.Unlock() // 解互斥锁
wg.Done()
}
func read() {
// lock.Lock() // 加互斥锁
rwlock.RLock() // 加读锁
time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
rwlock.RUnlock() // 解读锁
// lock.Unlock() // 解互斥锁
wg.Done()
}
func main() {
start := time.Now()
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
for i := 0; i < 1000; i++ {
wg.Add(1)
go read()
}
wg.Wait()
end := time.Now()
fmt.Println(end.Sub(start))
}
在代码中生硬的使用time.Sleep肯定是不合适的,Go语言中可以使用sync.WaitGroup来实现并发任务的同步。
sync.Once实现单例模式
Go语言中的sync包中提供了一个针对只执行一次场景的解决方案–sync.Once。
sync.Once只有一个Do方法,其签名如下:
func (o *Once) Do(f func()) {}
备注:如果要执行的函数f需要传递参数就需要搭配闭包来使用。
package main
import "sync"
type singleton struct{}
var instance *singleton
var once sync.Once
func GetInstance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
func main() {
GetInstance()
}
sync.Map
Go语言中内置的map不是并发安全的。
package main
import (
"fmt"
"strconv"
"sync"
)
var safeMap = sync.Map{}
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 20; i++ {
go func(n int) {
wg.Add(1)
key := strconv.Itoa(n)
safeMap.Store(key, n)
val, ok := safeMap.Load(key) //
if ok {
fmt.Println("key:", key, "val:", val)
}
wg.Done()
}(i)
}
wg.Wait()
}
普通的map则会报错。
atomic原子变量
测试一下对于变量自增操作,加锁、使用atomic原子变量三个的性能差别:
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
//Counter 定义一个公共的接口
type Counter interface{
Increment()
Load() int64
}
//NonLockCounter 无锁的自增变量结构体
type NonLockCounter struct{
val int64
}
//Increment NonLockCounter结构体的自增方法
func(n *NonLockCounter)Increment(){
n.val++
}
//Load NonLockCounter的Load方法
func(n *NonLockCounter)Load()int64{
return n.val
}
//LockCounter 带互斥锁的自增变量结构体
type LockCounter struct{
val int64
lock sync.Mutex
}
//Increment 带互斥锁的自增变量结构体的自增方法
func(l *LockCounter)Increment(){
l.lock.Lock()
defer l.lock.Unlock()
l.val++
}
//Load 带互斥锁的自增变量结构体的Load方法
func(l* LockCounter)Load()int64{
l.lock.Lock()
defer l.lock.Unlock()
return l.val
}
//AtomicCounter 使用atomic库的原子操作的结构体
type AtomicCounter struct{
val int64
}
//Increment 原子操作的自增方法
func(a *AtomicCounter)Increment(){
atomic.AddInt64(&a.val,1)
}
//Load 原子操作的Load方法
func(a *AtomicCounter)Load()int64{
return atomic.LoadInt64(&a.val)
}
func test(c Counter){
wg:=sync.WaitGroup{}
start:=time.Now()
for i:=0;i<1000;i++{
wg.Add(1)
go func(){
c.Increment()
wg.Done()
}()
}
wg.Wait()
end:=time.Now()
fmt.Println(c.Load(),end.Sub(start))
}
func main(){
c1:=NonLockCounter{}
c2:=LockCounter{}
c3:=AtomicCounter{}
test(&c1)
test(&c2)
test(&c3)
}
971 1.9977ms
1000 1.001ms
1000 0s
atomic包提供了底层的原子级内存操作,对于同步算法的实现很有用。这些函数必须谨慎地保证正确使用。除了某些特殊的底层应用,使用通道或者sync包的函数/类型实现同步更好。