在Go语言里面,你不仅可以使用原子函数和互斥锁来保证对共享资源的安全访问以消除竞争状态,
还可以使用通道,通过发送和接收需要共享的资源,在goroutine之间做同步。
当一个资源需要在goroutine之间共享时,通道在goroutine之间架起了一个管道,并提供了确保同步交换数据的机制。
声明通道时,需要指定将要被共享的数据类型。可以通过通道共享内置类型、命名类型、结构类型和引用类型的值或者指针。
在Go语言中需要使用内置函数make来创建一个通道。
//使用make创建通道 //无缓冲的整型通道 unbuffered := make(chan int) //有缓冲的字符串通道 buffered := make(chan string, 10)
向通道发送值或者指针需要用到<-操作符。
//向通道发送值 //有缓冲的字符串通道 buffered := make(chan string, 10) //通过通道发送一个字符串 buffered <- "gopher"
我们创建了一个有缓冲的通道,数据类型是字符串,包含一个10个值的缓冲区。
之后,我们通过通道发送字符串”gopher“。为了让另一个goroutine可以从通道里接收到这个字符串,我们依旧使用<-操作符,但这次是一元操作符。
//从通道里接收一个字符串 value := <-buffered
(1)无缓冲的通道
无缓冲通道是指在接收前没有能力保存任何值得通道。
这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收任务。
如果两个giroutine没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。
下面说明一下无缓冲通道是如何来共享数据的:
两个goroutine(假设为A和B)都到达通道,但哪个都没有开始执行发送或者接收。
假设goroutine A向通道发送了数据,goroutine A会在通道中被锁住,直到交换完成。
goroutine B会从通道里接收数据,goroutine B一样会在通道中被锁住,直到交换完成。
随后会完成数据交换,随后会释放两个goroutine。
示例1:
//如何用无缓冲的通道来模拟2个goroutine间的网球比赛 package main import ( "fmt" "math/rand" "sync" "time" ) //wg用来等待程序结束 var wg sync.WaitGroup func init() { rand.Seed(time.Now().UnixNano()) } func main() { //创建一个无缓冲的通道 court := make(chan int) wg.Add(2) //启动两个选手 go player("kebi", court) go player("maoxian", court) //发球,向通道中发送数据 court <- 1 //等待游戏结束 wg.Wait() } //player模拟一个选手在打网球 func player(name string, court chan int) { defer wg.Done() for { //等待数据发过来 ball, ok := <-court if !ok { //检测通道是否为false,如果是false表示通道已经关闭 //如果通道被关闭了,就知道 fmt.Printf("Player %s Won ", name) return } //选个随机数,然后用这个数来判断我们是否丢球 n := rand.Intn(100) if n%13 == 0 { fmt.Printf("Player %s Missed ", name) //关闭通道表示已经输了 close(court) return } //显式击球数,并加一 fmt.Printf("Player %s Hit %d ", name, ball) ball++ court <- ball } } /* Player maoxian Hit 1 Player kebi Hit 2 Player maoxian Hit 3 Player kebi Hit 4 Player maoxian Missed Player kebi Won */
示例2:
//如何用无缓冲的通道来模拟4个goroutine间的接力比赛 package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func main() { baton := make(chan int) //为最后一位跑步者将计数加一 wg.Add(1) //第一位跑步者持有接力棒 go Runner(baton) 开始比赛 baton <- 1 wg.Wait() } func Runner(baton chan int) { var newRunner int //等待接力棒 runner := <-baton //开始跑步 fmt.Printf("runner %d running With Baton ", runner) //创建下一位跑步者 if runner != 4 { newRunner = runner + 1 fmt.Printf("runner %d To the Line ", newRunner) go Runner(baton) } //围绕跑道跑 time.Sleep(100 * time.Millisecond) //比赛结束了吗? if runner == 4 { fmt.Printf("runner %d Finished,Race Over ", runner) wg.Done() return } //将接力棒交给下一位跑步者 fmt.Printf("Runner %d Exchange With Runner %d ", runner, newRunner) baton <- newRunner } /* runner 1 running With Baton runner 2 To the Line Runner 1 Exchange With Runner 2 runner 2 running With Baton runner 3 To the Line Runner 2 Exchange With Runner 3 runner 3 running With Baton runner 4 To the Line Runner 3 Exchange With Runner 4 runner 4 running With Baton runner 4 Finished,Race Over */
(2)有缓冲的通道
有缓冲的通道是一种在被接收前能存储一个或者多个值的通道,这种类型的通道并不强制要求goroutine之间必须同时完成发送和接收。
通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。
只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会被阻塞。
这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:
无缓冲通道保证进行发送和接收的goroutine会在同一时间进行数据交换,有缓冲的通道没有这种保证。
//展示如何使用有缓冲的通道和固定数目的goroutine来处理一堆工作 package main import ( "fmt" "math/rand" "sync" "time" ) const ( numberGoroutines = 4 //使用的goroutine的数量 taskLoad = 10 //要处理的工作的数量 ) var wg sync.WaitGroup func init() { rand.Seed(time.Now().Unix()) } func main() { tasks := make(chan string, taskLoad) //启动goroutine来处理工作 wg.Add(numberGoroutines) for gr := 1; gr <= numberGoroutines; gr++ { go worker(tasks, gr) } //增加一组要完成的工作 for post := 1; post <= taskLoad; post++ { tasks <- fmt.Sprintf("Task : %d", post) } //当所有工作处理完毕时关闭通道 close(tasks) wg.Wait() } func worker(tasks chan string, worker int) { defer wg.Done() for { //等待分配工作 task, ok := <-tasks if !ok { //通道已空且被关闭 fmt.Printf("Worker: %d : Shutting Down ", worker) return } //开始工作 fmt.Printf("Worker: %d : Started %s ", worker, task) //随机等待一段时间来模拟工作 sleep := rand.Int63n(100) time.Sleep(time.Duration(sleep) * time.Millisecond) //显式完成了工作 fmt.Printf("Worker: %d : Completed %s ", worker, task) } } /* Worker: 4 : Started Task : 4 Worker: 2 : Started Task : 1 Worker: 1 : Started Task : 3 Worker: 3 : Started Task : 2 Worker: 3 : Completed Task : 2 Worker: 3 : Started Task : 5 Worker: 1 : Completed Task : 3 Worker: 1 : Started Task : 6 Worker: 4 : Completed Task : 4 Worker: 4 : Started Task : 7 Worker: 2 : Completed Task : 1 Worker: 2 : Started Task : 8 Worker: 4 : Completed Task : 7 Worker: 4 : Started Task : 9 Worker: 2 : Completed Task : 8 Worker: 2 : Started Task : 10 Worker: 3 : Completed Task : 5 Worker: 3 : Shutting Down Worker: 1 : Completed Task : 6 Worker: 1 : Shutting Down Worker: 2 : Completed Task : 10 Worker: 2 : Shutting Down Worker: 4 : Completed Task : 9 Worker: 4 : Shutting Down */