如果两个 goroutine 没有同时准备好,通道会导致第一个发送或接收的 goroutine 阻塞等待。这种向通道发送和从通道接收的交互本质上是同步的。这些操作中的任何一个都不能脱离另一个而存在。
阻塞是指由于某种原因数据没有到达,当前协程(线程)继续等待,直到满足某个条件才解除阻塞。
同步是指在两个或多个协程(线程)之间保持数据内容一致性的机制。
为了清楚起见,让我们看两个完整的示例。这两个示例都使用无缓冲通道在两个 goroutine 之间同步交换数据。
[示例1] 在一场网球比赛中,两名球员在他们之间来回传球。球员总是处于两种状态之一:等待接球或将球击向对手。如下面的代码所示,您可以使用两个 goroutine 来模拟网球比赛,并使用无缓冲通道来模拟球的往返行程。
// このサンプルプログラムは、バッファのないチャネルを使って、
// 2つのゴルーチン間のテニスの試合をシミュレートする方法を示しています。
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
// プログラムの終了を待つためのwg
var wg sync.WaitGroup
func init() {
rand.Seed(time.Now().UnixNano())
}
// mainは、すべてのGoプログラムのエントリーポイントです。
func main() {
// バッファのないチャネルを作成する
court := make(chan int)
// 2つのゴルーチンを待つため、カウントを2に増やす
wg.Add(2)
// 2人の選手を開始する
go player("Nadal", court)
go player("Djokovic", court)
// サーブを開始する
court <- 1
// ゲームの終了を待つ
wg.Wait()
}
// playerは、テニスを打っている選手をシミュレートします。
func player(name string, court chan int) {
// 関数を終了する際にDoneを呼び出して、main関数に作業が完了したことを通知します。
defer wg.Done()
for {
// 玉が打たれてくるのを待ちます
ball, ok := <-court
if !ok {
// チャネルが閉じられた場合、私たちは勝ちました
fmt.Printf("Player %s Won\n", name)
return
}
// ランダムな数字を選んで、その数字でボールを落とすかどうかを判断します。
n := rand.Intn(100)
if n%13 == 0 {
fmt.Printf("Player %s Missed\n", name)
// チャネルを閉じて、私たちは負けましたと示します。
close(court)
return
}
// 打球数を表示し、打球数を1つ増やします。
fmt.Printf("Player %s Hit %d\n", name, ball)
ball++
// ボールを相手に打つ
court <- ball
}
} 当您运行该程序时,输出将如下所示。
Player Nadal Hit 1
Player Djokovic Hit 2
Player Nadal Hit 3
Player Djokovic Missed
Player Nadal Won
代码说明如下:
- 第 22 行创建了一个 int 类型的无缓冲通道,允许两个 goroutine 在击球时彼此同步。
- 第 28 行和第 29 行创建了两个参与竞赛的 goroutine。此时,两个 goroutine 都被阻塞等待击球。
- 在第 32 行,球被发送到通道,程序开始运行游戏,直到 goroutine 输掉游戏。
- for 语句的无限循环位于第 43 行。这个循环包括玩游戏的过程。
- 在第 45 行,goroutine 从通道接收数据。该数据用于指示球等待。此接收操作会锁定 goroutine,直到数据发送到通道。当通道的接收操作返回时。
- 第 46 行检查 ok 标志是否为 false。如果该值为 false,则通道关闭并且游戏结束。
- 在第 53 到 60 行中,生成一个随机数来确定 goroutine 是否会被球击中。
- 如果 goroutine 错过了球,第 58 行将关闭通道。然后两个 goroutine 返回,执行延迟的 Done,然后程序退出。
- 当球被击中时,第 64 行将球的值增加 1,第 67 行将球返回到通道以发送给另一个玩家。此时两个 goroutine 都被锁定,直到交换完成。
[示例 2] 使用不同模式、使用无缓冲通道以及在 goroutine 之间同步数据来模拟接力赛。在接力赛中,四名选手轮流绕跑道跑。 2、3、4 号选手必须从前面的选手手中接过接力棒才能出发。比赛最重要的环节就是接力棒的传递,接力棒必须同时传递。同步接力棒时,参加接力赛的两名运动员必须同时做好接力棒的准备。代码如下所示。
// このサンプルプログラムは、バッファのないチャネルを使って
// 4つのgoroutineの間でリレー競走をシミュレートする方法を示します
package main
import (
"fmt"
"sync"
"time"
)
// プログラムの終了を待つためのwg
var wg sync.WaitGroup
// mainはすべてのGoプログラムのエントリーポイントです
func main() {
// バッファのないチャネルを作成する
baton := make(chan int)
// 最後のランナーのためにカウントを+1する
wg.Add(1)
// 最初のランナーがバトンを持ちます
go Runner(baton)
// 競争を開始する
baton <- 1
// 競争が終了するのを待つ
wg.Wait()
}
// Runnerは、リレー競走の一人のランナーをシミュレートします
func Runner(baton chan int) {
var newRunner int
// バトンを待っています
runner := <-baton
// ラップトラックを回り始めます
fmt.Printf("Runner %d Running With Baton\n", runner)
// 次のランナーを作成する
if runner != 4 {
newRunner = runner + 1
fmt.Printf("Runner %d To The Line\n", newRunner)
go Runner(baton)
}
// ラップトラックを回る
time.Sleep(100 * time.Millisecond)
// 競争が終了しましたか?
if runner == 4 {
fmt.Printf("Runner %d Finished, Race Over\n", runner)
wg.Done()
return
}
// バトンを次のランナーに渡す
fmt.Printf("Runner %d Exchange With Runner %d\n",
runner,
newRunner)
baton <- newRunner
} 当您运行该程序时,输出将如下所示。
Runner 1 Running With Baton
Runner 1 To The Line
Runner 1 Exchange With Runner 2
Runner 2 Running With Baton
Runner 2 To The Line
Runner 2 Exchange With Runner 3
Runner 3 Running With Baton
Runner 3 To The Line
Runner 3 Exchange With Runner 4
Runner 4 Running With Baton
Runner 4 Finished, Race Over
代码说明如下:
- 第17行创建了一个int类型的无缓冲通道接力棒,用于同步传递接力棒。
- 第 20 行向 WaitGroup 添加 1,以便 main 函数等待直到最后一个运行程序完成。
- 第 23 行创建了一个 goroutine,通知您第一个跑步者已到达赛道。
- 第 26 行,接力棒传递给赛跑者,比赛开始。
- 在第 29 行,主函数在 WaitGroup 中阻塞并等待最后一个跑步者完成比赛。
- 在第 37 行,goroutine 对接力通道执行接收操作,表明它正在等待接力。
- 第 46 行,当接力棒传递时,会创建新的运行者,直到 goroutine 成为第四个运行者,准备好接收下一个接力棒。
- 在第 50 行中,跑步者绕跑道运行 100 毫秒。
- 在第 55 行,当第四个跑步者完成比赛时,Done 被调用,WaitGroup 减 1,并且 goroutine 返回。
- 第64行,如果该goroutine不是第四个跑步者,则接力棒会传递给下一个已经在等待的跑步者。此时,goroutine 被锁定,直到切换完成。
这两个示例都使用无缓冲通道来模拟网球和接力赛来同步 goroutine。该代码很容易阅读,因为代码的流程与现实世界中这两个活动的流程完全相同。
现在您已经了解了无缓冲通道的工作原理,下一节将介绍缓冲通道。




![2021 年如何设置 Raspberry Pi Web 服务器 [指南]](https://i0.wp.com/pcmanabu.com/wp-content/uploads/2019/10/web-server-02-309x198.png?w=1200&resize=1200,0&ssl=1)

