Go语言的并发
原子操作
可以利用sync包中的Mutex对于资源进行加锁,从而保证资源的原子性。但其实Go官方并不推荐这种方法,更提倡用通信的方法来进行内存的共享。
不要以共享内存的方式来通信,作为替代,我们应该以通信的手段来共享内存
基于信道的通信
channel是在Goroutine之间进行同步的主要方法,读写通过“->”完成
通过make函数进行声明
可能出现错误的操作
-
关闭一个未初始化(nil) 的 channel 会产生 panic
-
重复关闭同一个 channel 会产生 panic
-
向一个已关闭的 channel 中发送消息会产生 panic
-
从已关闭的 channel 读取消息不会产生 panic,且能读出 channel 中还未被读取的消息,若消息均已读出,则会读到类型的零值。从一个已关闭的 channel 中读取消息永远不会阻塞,并且会返回一个为 false 的 ok-idiom,可以用它来判断 channel 是否关闭
-
关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息
无缓存
无论是写入还是读取channel都会阻塞,直到另外一个goroutine读取或写入这个channel
带缓存
当缓存满时,写入channel会阻塞,直到另外一个goroutine取走一个数据。
当缓存为空时,读取channel会阻塞,直到另外一个goroutine写入一个数据。
使用select同时监听多个channel
select {
case <- ch1:
...
case <- ch2:
...
case ch3 <- 10;
...
default:
...
}
如果没有定义default操作,那么当所有的channel都阻塞的时候,select也会阻塞。
这个只能监听一次消息,如果想要一直监听的话。
msgCh := make(chan struct{})
quitCh := make(chan struct{})
for {
select {
case <- msgCh:
doWork()
case <- quitCh:
finish()
return
}
一直循环,直到收到quit信息
经典例子
package main
import "fmt"
func generateNum() chan int {
ch := make(chan int)
go func() {
for i := 2; ; i++ {
ch <- i
}
}()
return ch
}
func filter(in <-chan int, prime int) chan int {
out := make(chan int)
go func() {
for {
if i := <-in; i%prime != 0 {
out <- i
}
}
}()
return out
}
func main() {
ch := generateNum()
for i := 0; i < 100; i++ {
prime := <-ch
fmt.Printf("%v:%v\n", i+1, prime)
ch = filter(ch, prime)
}
}
这个例子是书上的,但我感觉很奇怪。对于filter循环调用了100次,每次都会创建一个永远不会退出的死循环,也就是说有100个goroutine在执行完全一样的操作。感觉应该有更好的写法。
goroutine的退出
当对于一个channel执行close操作的时候,读取这个channel的地方都会接受到一个0值和一个失败标记。
func worker(channel chan bool) {
for true {
select {
default:
fmt.Println("hello")
case <-channel:
fmt.Println("done")
break
}
}
}
func main() {
cannel := make(chan bool)
for i := 0; i < 10; i++ {
go worker(cannel)
}
time.Sleep(time.Second)
close(cannel)
}