上一章我们我们已经讲解了go语言的消费者,这一章我们讲解生产者,这一章的内容涉及到通过channel进行信号同步。信号同步会遇到时间迟滞,子协程存在竞争,会导致偶然的死锁,如何解决这个同步问题,让程序健壮地并发是我们这一章的重点内容。
直接上代码。
// gochan_1.go
package main
import (
"fmt"
)
func producer(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
func main() {
ch := make(chan int)
go producer(ch)
for num := range ch {
fmt.Println("num=", num)
}
fmt.Println("game over!")
}
我们要让生产者无限制生产数据,直到接到信号停止生产。
这就需要增加一个channel进行信号同步,是否能达到目的呢,我们先看看代码。
// goifok.go
package main
import (
"fmt"
"time"
)
func main() {
quit := make(chan bool)
go func() {
time.Sleep(time.Second * 3)
quit <- true
}()
if q, ok := <-quit; ok {
fmt.Println("ok", q)
} else {
fmt.Println("not ok")
}
fmt.Println("Hello World!")
}
我们增加了一个叫quit的channel进行信号同步,这个通道需要传入一个bool值,不管这个值是true or false,只要有值就能达到目的。增加这个信号就能达到目的了吗?我们看看运行结果。
出现了死锁。这是什么原因呢?这是由于要先生产数据,只有满足条件后才会有退出信号通过通道quit传进来,但是由于没有值传进来,信号就会发生堵塞。这就是死锁的原因。对于有两个以上信号的我们要用select来进行条件控制。
// gochan_1.go
package main
import (
"fmt"
"time"
)
func producer(ch chan int, quit chan bool) {
i := 1
for {
select {
case <-quit:
close(ch)
fmt.Println("quit")
return
default:
ch <- i
i++
}
}
}
func main() {
ch := make(chan int)
quit := make(chan bool)
go producer(ch, quit)
for num := range ch {
if num >= 5 {
quit <- true
close(quit)
}
time.Sleep(time.Second)
fmt.Println("num=", num)
}
fmt.Println("game over!")
}
从上面的运行结果看,没有发生死锁,是不是可以结束了呢?多试几次仍然会发生死锁。多运行几次 go run -race gochan_1.go会发生堵塞,程序不能结束。
什么原因导致这个偶发的错误呢?
我们需要健壮的运行并发程序,怎么避免这种情况呢?下一节继续讲解这个问题。