参考:Go语言中文网
参考:浅谈 Golang sync 包的相关使用方法
老实说,看了go提供的这些锁的包,和java中的concurrent包其实很多作用是类似的,所以说其实很多语言的基础架构都是类似的,理解到源码层次再回头去学习其他的语言就不会那么难了。
壹、Mutex - 互斥锁
1、方法说明
1、结构体
一个互斥锁只能同时被一个 goroutine 锁定,其它 goroutine 将阻塞直到互斥锁被解锁(重新争抢对互斥锁的锁定。
// A Mutex is a mutual exclusion lock.
// The zero value for a Mutex is an unlocked mutex.
//
// A Mutex must not be copied after first use.
type Mutex struct {
state int32
sema uint32
}
2、Lock()
对资源进行加锁,如果没有获取到锁则会一直锁定,等待锁释放,再进行获取
// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock()
3、Unlock()
释放锁,如果没有被锁定而调用这个方法就会报错。
// Unlock unlocks m.
// It is a run-time error if m is not locked on entry to Unlock.
//
// A locked Mutex is not associated with a particular goroutine.
// It is allowed for one goroutine to lock a Mutex and then
// arrange for another goroutine to unlock it.
func (m *Mutex) Unlock()
2、使用示例
加锁的意思是:锁定互斥锁,而不是去锁定一段代码。也就是说,当代码执行到有锁的地方时,它获取不到互斥锁的锁定,会阻塞在那里,从而达到控制同步的目的。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
ch := make(chan struct{}, 2)
var mutex sync.Mutex
go func() {
mutex.Lock()
defer mutex.Unlock()
fmt.Println("goroutine1: 我会锁定大概 2s")
time.Sleep(time.Second * 2)
fmt.Println("goroutine1: 我解锁了,你们去抢吧")
ch <- struct{}{}
}()
go func() {
fmt.Println("groutine2: 等待解锁")
mutex.Lock()
defer mutex.Unlock()
fmt.Println("goroutine2: 哈哈,我锁定了")
ch <- struct{}{}
}()
// 等待 goroutine 执行结束
for i := 0; i < 2; i++ {
<-ch
}
}
贰、RWMutex - 读写锁
1、方法说明
读写锁的锁定会遵循一些规则:
- 同时只能有一个 goroutine 能够获得写锁定。
- 同时可以有任意多个 gorouinte 获得读锁定。
- 同时只能存在写锁定或读锁定(读和写互斥)。
// There is a modified copy of this file in runtime/rwmutex.go.
// If you make any changes here, see if you should make them there.
// A RWMutex is a reader/writer mutual exclusion lock.
// The lock can be held by an arbitrary number of readers or a single writer.
// The zero value for a RWMutex is an unlocked mutex.
//
// A RWMutex must not be copied after first use.
//
// If a goroutine holds a RWMutex for reading and another goroutine might
// call Lock, no goroutine should expect to be able to acquire a read lock
// until the initial read lock is released. In particular, this prohibits
// recursive read locking. This is to ensure that the lock eventually becomes
// available; a blocked Lock call excludes new readers from acquiring the
// lock.
type RWMutex struct {
w Mutex // held if there are pending writers
writerSem uint32 // semaphore for writers to wait for completing readers
readerSem uint32 // semaphore for readers to wait for completing writers
readerCount int32 // number of pending readers
readerWait int32 // number of departing readers
}
1、Lock()
对写操作进行锁定
// Lock locks rw for writing.
// If the lock is already locked for reading or writing,
// Lock blocks until the lock is available.
func (rw *RWMutex) Lock()
2、UnLock()
对写操作进行释放锁
// Unlock unlocks rw for writing. It is a run-time error if rw is
// not locked for writing on entry to Unlock.
//
// As with Mutexes, a locked RWMutex is not associated with a particular
// goroutine. One goroutine may RLock (Lock) a RWMutex and then
// arrange for another goroutine to RUnlock (Unlock) it.
func (rw *RWMutex) Unlock()
3、RLock()
对读操作进行加锁
// RLock locks rw for reading.
//
// It should not be used for recursive read locking; a blocked Lock
// call excludes new readers from acquiring the lock. See the
// documentation on the RWMutex type.
func (rw *RWMutex) RLock()
4、RUnlock
对读操作进行释放锁
// RUnlock undoes a single RLock call;
// it does not affect other simultaneous readers.
// It is a run-time error if rw is not locked for reading
// on entry to RUnlock.
func (rw *RWMutex) RUnlock()
2、实际示例
分别开启50个读写协程,模拟读写争取读写锁的情况。
package main
import (
"fmt"
"math/rand"
"sync"
)
var count int
var rw sync.RWMutex
func main() {
ch := make(chan struct{}, 10)
for i := 0; i < 50; i++ {
go read(i, ch)
}
for i := 0; i < 50; i++ {
go write(i, ch)
}
for i := 0; i < 10; i++ {
<-ch
}
}
func read(n int, ch chan struct{}) {
rw.RLock()
fmt.Printf("goroutine %d 进入读操作...\n", n)
v := count
fmt.Printf("goroutine %d 读取结束,值为:%d\n", n, v)
rw.RUnlock()
ch <- struct{}{}
}
func write(n int, ch chan struct{}) {
rw.Lock()
fmt.Printf("goroutine %d 进入写操作...\n", n)
v := rand.Intn(1000)
count = v
fmt.Printf("goroutine %d 写入结束,新值为:%d\n", n, v)
rw.Unlock()
ch <- struct{}{}
}
叁、WaitGroup
1、方法说明
WaitGroup 用于等待一组 goroutine 结束,也就是说让一组操作同时结束之后才进行下一步的操作。跟java中的CyclicBarrier有点类似
// A WaitGroup waits for a collection of goroutines to finish.
// The main goroutine calls Add to set the number of
// goroutines to wait for. Then each of the goroutines
// runs and calls Done when finished. At the same time,
// Wait can be used to block until all goroutines have finished.
//
// A WaitGroup must not be copied after first use.
type WaitGroup struct {
noCopy noCopy
state1 [3]uint32
}
1、Add()
添加要等待的协程数量,如果传入负数会报错
// Add adds delta, which may be negative, to the WaitGroup counter.
// If the counter becomes zero, all goroutines blocked on Wait are released.
// If the counter goes negative, Add panics.
//
// Note that calls with a positive delta that occur when the counter is zero
// must happen before a Wait. Calls with a negative delta, or calls with a
// positive delta that start when the counter is greater than zero, may happen
// at any time.
// Typically this means the calls to Add should execute before the statement
// creating the goroutine or other event to be waited for.
// If a WaitGroup is reused to wait for several independent sets of events,
// new Add calls must happen after all previous Wait calls have returned.
// See the WaitGroup example.
func (wg *WaitGroup) Add(delta int)
2、Done()
运行完一个协程,等待的队列数量减一。
// Done decrements the WaitGroup counter by one.
func (wg *WaitGroup) Done()
3、Wait()
等待队列中协程数量等于0,才运行下面的代码。
// Wait blocks until the WaitGroup counter is zero.
func (wg *WaitGroup) Wait()
2、实际示例
每个协程等待5秒,直到所有的协程运行完毕,才会执行wait下面的代码。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
for i, s := range seconds {
// 计数加 1
wg.Add(1)
go func(i, s int) {
// 计数减 1
defer wg.Done()
fmt.Printf("goroutine%d 结束\n", i)
time.Sleep(5 * time.Second)
}(i, s)
}
// 等待执行结束
wg.Wait()
fmt.Println("所有 goroutine 执行结束")
}
肆、Once - 只执行一次
1、方法说明
使用 sync.Once 对象可以使得函数多次调用只执行一次。
// Once is an object that will perform exactly one action.
type Once struct
只有在第一次调用的时候才会被执行,后面重复调用不会执行
// Do calls the function f if and only if Do is being called for the
// first time for this instance of Once. In other words, given
// var once Once
// if once.Do(f) is called multiple times, only the first call will invoke f,
// even if f has a different value in each invocation. A new instance of
// Once is required for each function to execute.
//
// Do is intended for initialization that must be run exactly once. Since f
// is niladic, it may be necessary to use a function literal to capture the
// arguments to a function to be invoked by Do:
// config.once.Do(func() { config.init(filename) })
//
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
//
// If f panics, Do considers it to have returned; future calls of Do return
// without calling f.
//
func (o *Once) Do(f func())
2、实际示例
package main
import (
"fmt"
"sync"
)
func main() {
var once sync.Once
onceBody := func() {
fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
}
伍、Pool - 临时对象池
1、方法说明
参考:深入Golang之sync.Pool详解
我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池。 sync.Pool是可伸缩的,并发安全的。其大小仅受限于内存的大小,可以被看作是一个存放可重用对象的值的容器。 设计的目的是存放已经分配的但是暂时不用的对象,在需要用到的时候直接从pool中取。
任何存放区其中的值可以在任何时候被删除而不通知,在高负载下可以动态的扩容,在不活跃时对象池会收缩。
使用说明:
- sync.Pool是一个可以存或取的临时对象集合
- sync.Pool可以安全被多个线程同时使用,保证线程安全
- 注意、注意、注意,sync.Pool中保存的任何项都可能随时不做通知的释放掉,所以不适合用于像- socket长连接或数据库连接池。
- sync.Pool主要用途是增加临时对象的重用率,减少GC负担。
1、New
当获取不到临时对象时自动创建一个(不会主动加入到 Pool 中),即如果取不到,默认的返回值。
// New optionally specifies a function to generate
// a value when Get would otherwise return nil.
// It may not be changed concurrently with calls to Get.
New func() interface{}
2、Get()
从临时对象池取出数据
// Get selects an arbitrary item from the Pool, removes it from the
// Pool, and returns it to the caller.
// Get may choose to ignore the pool and treat it as empty.
// Callers should not assume any relation between values passed to Put and
// the values returned by Get.
//
// If Get would otherwise return nil and p.New is non-nil, Get returns
// the result of calling p.New.
func (p *Pool) Get() interface{}
3、Put()
向临时对象添加数据
// Put adds x to the pool.
func (p *Pool) Put(x interface{})
2、实际示例
package main
import (
"fmt"
"sync"
)
func main() {
p := &sync.Pool{
New: func() interface{} {
return 0
},
}
p.Put("jiangzhou")
p.Put(123456)
fmt.Println(p.Get())
fmt.Println(p.Get())
fmt.Println(p.Get())
}
陆、Cond - 条件锁
1、方法说明
Cond 实现了一个条件变量,在 Locker 的基础上增加的一个消息通知的功能,保存了一个通知列表,用来唤醒一个或所有因等待条件变量而阻塞的 Go 程,以此来实现多个 Go 程间的同步。
// Cond implements a condition variable, a rendezvous point
// for goroutines waiting for or announcing the occurrence
// of an event.
//
// Each Cond has an associated Locker L (often a *Mutex or *RWMutex),
// which must be held when changing the condition and
// when calling the Wait method.
//
// A Cond must not be copied after first use.
type Cond struct
1、NewCond()
创建一个带锁的条件变量,Locker 通常是一个 *Mutex 或 *RWMutex
func NewCond(l Locker) *Cond
2、Broadcast()
唤醒所有因等待条件变量 c 阻塞的 goroutine
func (c *Cond) Broadcast()
3、Signal()
唤醒一个因等待条件变量 c 阻塞的 goroutine
func (c *Cond) Signal()
4、Wait()
等待 c.L 解锁并挂起 goroutine,在稍后恢复执行后,Wait 返回前锁定 c.L,只有当被 Broadcast 和 Signal 唤醒,Wait 才能返回
func (c *Cond) Wait()
2、实际示例
关键的地方在于配套使用,也就是说wait的协程会等待signal来唤醒,如果没有唤醒则会一直阻塞。
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执行完毕
}