前言:
在前面并发性能对比的文章中,咱们能够看到Golang处理大并发的能力十分强劲,并且开发也特别方便,只须要用go关键字便可开启一个新的协程。数组
但当多个goroutine同时进行处理的时候,就会遇到同时抢占一个资源的状况(并发都会遇到的问题),因此咱们但愿某个goroutine等待另外一个goroutine处理完某一个步骤以后才能继续。sync包就是为了让goroutine同步而出现的。固然还能够使用channel实现,这个后面会介绍到。多线程
锁:
锁有两种:互斥锁(mutex)和读写锁(RWMutex)并发
互斥锁: 当数据被加锁了以后,除次外的其余协程不能对数据进行读操做和写操做。 这个固然能解决并发程序对资源的操做。可是,效率上是个问题,由于当加锁后,其余协程只有等到解锁后才能对数据进行读写操做。性能
读写锁: 读数据的时候上读锁,写数据的时候上写锁。有写锁的时候,数据不可读不可写。有读锁的时候,数据可读,不可写。spa
两种锁的使用方式相同,这里就只列出互斥锁的代码:线程
package main import ( "sync" "time" "fmt" ) var num = 0 func main () { mu := &sync.Mutex{} for i:=0;i<10000;i++ { go func(){ mu.Lock() defer mu.Unlock() num += 1 }() } time.Sleep(time.Second) fmt.Println("num:", num) // 若是不加锁这里的num的值会是一个随机数而不是10000 }
Once:
有的时候,咱们启动多个相同goroutine,可是里面的某个操做我只但愿被执行一次,这个时候Once就上场了。code
package main import ( "fmt" "sync" "time" ) func main() { var once sync.Once one := func() { fmt.Println("just once") } for i := 0; i < 10; i++ { go func(a int) { once.Do(one) // 只是被执行一次 }(i) } time.Sleep(time.Millisecond*200) }
WaitGroup:
当某个操做或是某个goroutine须要等待一批goroutine执行完毕之后才继续执行,那么这种多线程(go里面说的线程就是goroutine)等待的问题就能够使用WaitGroup了。协程
代码以下:blog
package main import ( "sync" "fmt" "time" ) var waitGroup sync.WaitGroup func main () { for i := 0; i < 10; i++ { waitGroup.Add(1) // 添加须要等待goroutine的数量 go func() { fmt.Println("hehe") time.Sleep(time.Second) waitGroup.Done() // 减小须要等待goroutine的数量 至关于Add(-1) } () } waitGroup.Wait() // 执行阻塞,直到全部的须要等待的goroutine数量变成0 fmt.Println("over") }
Cond:
sync.Cond是用来控制某个条件下,goroutine进入等待时期,等待信号到来,而后从新启动。资源
代码以下:
package main import ( "fmt" "sync" "time" ) var locker = new(sync.Mutex) var cond = sync.NewCond(locker) func test(x int) { cond.L.Lock() //获取锁 cond.Wait()//等待通知 暂时阻塞 fmt.Println(x) time.Sleep(time.Second * 1) cond.L.Unlock()//释放锁 } func main() { for i := 0; i < 40; i++ { go test(i) } fmt.Println("start all") time.Sleep(time.Second * 3) fmt.Println("signal1") cond.Signal() // 下发一个通知随机给已经获取锁的goroutine time.Sleep(time.Second * 3) fmt.Println("signal2") cond.Signal()// 下发第二个通知随机给已经获取锁的goroutine time.Sleep(time.Second * 1) // 在广播以前要等一会,让全部线程都在wait状态 fmt.Println("broadcast") cond.Broadcast()//下发广播给全部等待的goroutine time.Sleep(time.Second * 60) }
上面代码有几个要点要特别说明一下:
1. 每一个Cond都必须有个与之关联的锁 // 见第9行
2. 协程方法里面一开始/结束都必须加/解锁 // 见第12行和16行
3. cond.Wait()时会自动解锁,当被唤醒时,又会加上锁。因此第2点提到必须加/解锁。
Channel
channel不只能够用来goroutine之间的通讯,也能够使goroutine同步完成协做。这点主要基于从channel取数据的时候,会阻塞当前goroutine这个特性。示例代码以下:
package main import ( "fmt" "time" ) var chan1 = make(chan string, 512) var arr1 = []string{"qq","ww","ee","rr","tt"} func chanTest1() { for _, v := range arr1 { chan1 <- v } close(chan1) // 关闭channel } func chanTest2() { for { getStr, ok := <- chan1 // 阻塞,直到chan1里面有数据 if !ok { // 判断channel是否关闭或者为空 return } fmt.Println(getStr) // 按数组顺序内容输出 } } func main () { go chanTest1() go chanTest2() time.Sleep(time.Millisecond*200) }