1. 开篇
我决定学习Go语言的时候,就做好了多线程编程的准备,而多线程编程,很重要的一点就是线程间通信。比如Java就用了notify进行通信,线程间通信是一个很重要的特性,没有通信那么多线程编程意义也不太大。
昨天我的学习笔记里提到了goroutine,我发现goroutine是如此简单的实现了所谓的并发,那么Go就一定有简单的方式实现goroutine间的通信,这个机制就是所谓的通道。
学过数据结构的人应该对FIFO这个特性特别熟悉,一般队列会有这个特点,而通道也可以理解成一个FIFO的队列,先被发送到通道中的值先被接收。
下面的图来自《Go IN ACTION》,很形象的说明了通道:
通道有一个很重要的运算符:<-,当运算符写在通道变量的右边时,表示将一个值传入通道,当运算符在通道变量左边时,表示从通道中取出值。
2. 例子
上面图中对无缓冲通道的描述倒是很像一个打乒乓球的过程,你来我往的。
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var wg sync.WaitGroup
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
ch := make(chan int)
wg.Add(2)
go playPingPong("Wong", ch)
go playPingPong("Lee", ch)
ch <- 1
wg.Wait()
}
func playPingPong(player string, ch chan int) {
defer wg.Done()
for {
ball, ok := <-ch
if !ok {
fmt.Printf("%s has won!\n", player)
return
}
n := rand.Intn(100)
if n % 13 == 0 {
fmt.Printf("player %s missed!\n", player)
close(ch)
return
}
ball++
fmt.Printf("play %s has hit the ball, his score is %d\n", player, ball)
ch <- ball
}
}
首先还是实现了两个goroutine,不同的是,方法里有一个参数是通道了。
ch<-1相当于发球的操作,此时ball这个变量就开始在通道里来回游走进行通信了,如果发现有人丢球了,就调度close方法将通道关闭,另外一个player发现通道关闭了,证明对方丢球了,因此也就能宣布自己胜利了。
今天学习的是无缓冲通道,发送和接收必须是同时的,这有点类似于程序设计中的同步操作。
下面写一个有缓冲的通道的例子,作为明天笔记的引子吧:
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
虽然在完成了向通道发送数值的操作之后,就关闭了通道,但是后面的三个打印语句仍然可以按照1,2,3的顺序打印出正确的数值。
有点像消息队列了,生产者只管生产,至于消费者如何是不关心的,即便是通道关闭了,不能再发送数值了,也不影响消费者消费。
这还有点像MySQL的传统异步复制,Master上推binlog的线程推完,服务器宕机了没有关系,反正已经写到Slave的中继日志上了,数据最终会达成一致。
3. 小结
每天写小结真累,但是Go语言真的是有意思,让我在尝试了多种语言之后终于决定将其作为未来主要的语言,用于辅助我的DBA工作。