golang中实现并发非常简单,只需在需要并发的函数前面添加关键字"go",但是如何处理go并发机制中不同goroutine之间的同步与通信,golang 中提供了sync包来解决相关的问题,当然还有其他的方式比如channel,原子操作atomic等等,这里先介绍sync包的用法。
sync 包提供了互斥锁这类的基本的同步原语.除 Once 和 WaitGroup 之外的类型大多用于底层库的例程。更高级的同步操作通过信道与通信进行。
type Cond
func NewCond(l Locker) *Cond
- func (c *Cond) Broadcast()
- func (c *Cond) Signal()
- func (c *Cond) Wait()
- type Locker
- type Mutex
- func (m *Mutex) Lock()
- func (m *Mutex) Unlock()
- type Once
- func (o *Once) Do(f func())
- type Pool
- func (p *Pool) Get() interface{}
- func (p *Pool) Put(x interface{})
- type RWMutex
- func (rw *RWMutex) Lock() 写锁
- func (rw *RWMutex) RLock() 读锁
- func (rw *RWMutex) RLocker() Locker
- func (rw *RWMutex) RUnlock()
- func (rw *RWMutex) Unlock()
- type WaitGroup
- func (wg *WaitGroup) Add(delta int)
- func (wg *WaitGroup) Done()
- func (wg *WaitGroup) Wait()
- golang中的同步是通过sync.WaitGroup来实现的.WaitGroup的功能:它实现了一个类似队列的结构,可以一直向队列中添加任务,当任务完成后便从队列中删除,如果队列中的任务没有完全完成,可以通过Wait()函数来出发阻塞,防止程序继续进行,直到所有的队列任务都完成为止.
-
WaitGroup总共有三个方法:
Add(delta int), Done(), Wait()。Add:添加或者减少等待goroutine的数量
Done:相当于Add(-1)
Wait:执行阻塞,直到所有的WaitGroup数量变成0 -
package main import ( "fmt" "sync" ) var waitgroup sync.WaitGroup func function(i int) { fmt.Println(i) waitgroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1) } func main() { for i := 0; i < 10; i++ { //每创建一个goroutine,就把任务队列中任务的数量+1 waitgroup.Add(1) go function(i) } //这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞 waitgroup.Wait() }
- 程序中需要并发,需要创建多个goroutine,并且一定要等这些并发全部完成后才继续接下来的程序执行.WaitGroup的特点是Wait()可以用来阻塞直到队列中的所有任务都完成时才解除阻塞,而不需要sleep一个固定的时间来等待
- 接下来看cond用法,很简单一个goroutine等待另外的goroutine发送通知唤醒。
-
package main import ( "fmt" "sync" "time" ) var locker = new(sync.Mutex) var cond = sync.NewCond(locker) var waitgroup sync.WaitGroup func test(x int) { cond.L.Lock() //等待通知,阻塞在此 cond.Wait() fmt.Println(x) time.Sleep(time.Second * 1) defer func() { cond.L.Unlock()//释放锁 waitgroup.Done() }() } func main() { for i := 0; i < 10; i++ { go test(i) waitgroup.Add(1); } fmt.Println("start all") time.Sleep(time.Second * 1) // 下发一个通知给已经获取锁的goroutine cond.Signal() time.Sleep(time.Second * 1) // 下发一个通知给已经获取锁的goroutine cond.Signal() time.Sleep(time.Second * 1) //下发广播给所有等待的goroutine fmt.Println("start Broadcast") cond.Broadcast() waitgroup.Wait() }
- 然后看Once,它可以保证代码段植段只被执行一次,可以用来实现单例。
-
package main import ( "fmt" "sync" "time" ) func main() { var once sync.Once onceBody := func() { time.Sleep(3e9) fmt.Println("Only once") } done := make(chan bool) for i := 0; i < 10; i++ { j := i go func(int) { once.Do(onceBody) fmt.Println(j) done <- true }(j) } <-done time.Sleep(3e9) }
- 用once可以保证上面的oncebody被执行一次,即使被多次调用,内部用一个atmoic int字段标示是否被执行过,和一个锁来实现,具体的可以看go的源码,syc目录下的once.go
- 然后说道pool,说白了就是一个对象池,这个类设计的目的是用来保存和复用临时对象,以减少内存分配,降低CG压力。
- Get返回Pool中的任意一个对象。如果Pool为空,则调用New返回一个新创建的对象。如果没有设置New,则返回nil。还有一个重要的特性是,放进Pool中的对象,会在说不准什么时候被回收掉。所以如果事先Put进去100个对象,下次Get的时候发现Pool是空也是有可能的。不过这个特性的一个好处就在于不用担心Pool会一直增长,因为Go已经帮你在Pool中做了回收机制。这个清理过程是在每次垃圾回收之前做的。垃圾回收是固定两分钟触发一次,而且每次清理会将Pool中的所有对象都清理掉!
-
func main(){ // 建立对象 var pipe = &sync.Pool{New:func()interface{}{return "Hello,BeiJing"}} // 准备放入的字符串 val := "Hello,World!" // 放入 pipe.Put(val) // 取出 log.Println(pipe.Get()) // 再取就没有了,会自动调用NEW log.Println(pipe.Get()) }
- 最后 RWMutex读写锁,RWMutex有两种锁写锁和读锁,用法也有不同,首先读锁可以同时加多个,但是写锁就不行当你试图加第二个写锁时就回导致当前的goroutine或者线程阻塞,但是这里的读锁就不会,那他有什么作用呢。
- 当有读锁,试图加写锁会阻塞,当有写锁,试图加读锁时会阻塞,当有读锁,试图加读锁时不会阻塞,这样有什么好处呢,当我们有一种数据读操作远远多于写操作时,当我们读时,如果加mutex或者写锁,会大大影响其他线程,因为我们大多数是读操作,因此如果我们加读锁,就不会影响其他线程的读操作,同时有线程写时也能保证数据的同步。最后一点很重要,不论是读锁还是写锁lock和unlock时一一对应的,unlock前一 定要有lock,就像c++的new和delete,一定要注意。
- 下 面看两个例子:来源:点击打开链接
- 随便读:注意此时此时不能写。
-
package main import ( "sync" "time" ) var m *sync.RWMutex func main() { m = new(sync.RWMutex) // 多个同时读 go read(1) go read(2) time.Sleep(2*time.Second) } func read(i int) { println(i,"read start") m.RLock() println(i,"reading") time.Sleep(1*time.Second) m.RUnlock() println(i,"read over") }
写的时候不可读也不可写package main import ( "sync" "time" ) var m *sync.RWMutex func main() { m = new(sync.RWMutex) // 写的时候啥也不能干 go write(1) go read(2) go write(3) time.Sleep(2*time.Second) } func read(i int) { println(i,"read start") m.RLock() println(i,"reading") time.Sleep(1*time.Second) m.RUnlock() println(i,"read over") } func write(i int) { println(i,"write start") m.Lock() println(i,"writing") time.Sleep(1*time.Second) m.Unlock() println(i,"write over") }