golang同步之sync包

来源:互联网 发布:电脑开热点软件 编辑:程序博客网 时间:2024/05/10 18:37

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 mainimport ("fmt""sync")var waitgroup sync.WaitGroupfunc function(i int) {fmt.Println(i)waitgroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)}func main() {for i := 0; i < 10; i++ {    //每创建一个goroutine,就把任务队列中任务的数量+1waitgroup.Add(1) go function(i)}//这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞waitgroup.Wait() }
     程序中需要并发,需要创建多个goroutine,并且一定要等这些并发全部完成后才继续接下来的程序执行.WaitGroup的特点是Wait()可以用来阻塞直到队列中的所有任务都完成时才解除阻塞,而不需要sleep一个固定的时间来等待
    接下来看cond用法,很简单一个goroutine等待另外的goroutine发送通知唤醒。
package mainimport ("fmt""sync""time")var locker = new(sync.Mutex)var cond = sync.NewCond(locker)var waitgroup sync.WaitGroupfunc 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)// 下发一个通知给已经获取锁的goroutinecond.Signal()time.Sleep(time.Second * 1)// 下发一个通知给已经获取锁的goroutinecond.Signal()time.Sleep(time.Second * 1)//下发广播给所有等待的goroutinefmt.Println("start Broadcast")cond.Broadcast()waitgroup.Wait()}
    然后看Once,它可以保证代码段植段只被执行一次,可以用来实现单例。
   
package mainimport ("fmt""sync""time")func main() {var once sync.OnceonceBody := func() {time.Sleep(3e9)fmt.Println("Only once")}done := make(chan bool)for i := 0; i < 10; i++ {j := igo func(int) {once.Do(onceBody)fmt.Println(j)done <- true}(j)}<-donetime.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())// 再取就没有了,会自动调用NEWlog.Println(pipe.Get())} 
   最后RWMutex读写锁,RWMutex有两种锁写锁和读锁,用法也有不同,首先读锁可以同时加多个,但是写锁就不行当你试图加第二个写锁时就回导致当前的goroutine或者线程阻塞,但是这里的读锁就不会,那他有什么作用呢。
   当有读锁,试图加写锁会阻塞,当有写锁,试图加读锁时会阻塞,当有读锁,试图加读锁时不会阻塞,这样有什么好处呢,当我们有一种数据读操作远远多于写操作时,当我们读时,如果加mutex或者写锁,会大大影响其他线程,因为我们大多数是读操作,因此如果我们加读锁,就不会影响其他线程的读操作,同时有线程写时也能保证数据的同步。最后一点很重要,不论是读锁还是写锁lock和unlock时一一对应的,unlock前一 定要有lock,就像c++的new和delete,一定要注意。
   下 面看两个例子:来源:点击打开链接
    随便读:注意此时此时不能写。
 
package mainimport (    "sync"    "time")var m *sync.RWMutexfunc 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 mainimport (    "sync"    "time")var m *sync.RWMutexfunc 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")}