使goroutine同步的方法总结
前言:
在前面并发性能对比的文章中,我们可以看到golang处理大并发的能力十分强劲,而且开发也特别方便,只需要用go关键字即可开启一个新的协程。
但当多个goroutine同时进行处理的时候,就会遇到同时抢占一个资源的情况(并发都会遇到的问题),所以我们希望某个goroutine等待另一个goroutine处理完某一个步骤之后才能继续。sync包就是为了让goroutine同步而出现的。当然还可以使用channel实现,这个后面会介绍到。
锁:
锁有两种:互斥锁(mutex)和读写锁(rwmutex)
互斥锁: 当数据被加锁了之后,除次外的其他协程不能对数据进行读操作和写操作。 这个当然能解决并发程序对资源的操作。但是,效率上是个问题,因为当加锁后,其他协程只有等到解锁后才能对数据进行读写操作。
读写锁: 读数据的时候上读锁,写数据的时候上写锁。有写锁的时候,数据不可读不可写。有读锁的时候,数据可读,不可写。
两种锁的使用方式相同,这里就只列出互斥锁的代码:
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就上场了。
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了。
代码如下:
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)
}
下一篇: .NetCore SignalR 踩坑记