1.交替打印数字和字母

193 阅读4分钟

问题描述:

使⽤两个 goroutine 交替打印序列,⼀个 goroutine 打印数字, 另外⼀个 goroutine 打印字⺟, 最终效果如下:

12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ2728

解题思路:

使⽤ channel 来控制打印的进度。使⽤两个 channel,来分别控制数字和字⺟的打印序列, 数字打印完成后通过 channel 通知字⺟打印, 字⺟打印完成后通知数字打印,然后周⽽复始的⼯作。

源码参考:

package main

import (
    "fmt"
    "sync"
)

func main() {
    // 创建两个无缓冲的channel, 用于goroutine之间的通信
    letter, number := make(chan bool), make(chan bool)
    // 创建一个WaitGroup, 用于等待goroutine 完成
    wait := sync.WaitGroup{}

    // 启动一个goroutine, 用于打印数字
    go func() {
       // 初始化变量i为1, 用于记录当前打印的数字
       i := 1
       // 无限循环,不停地打印数字
       for {
          // 使用select语句, 监听channel的状态
          select {
          case <-number:
             fmt.Print(i)
             i++
             fmt.Print(i)
             i++
             // 向letter channel发送信号, 通知打印字母的goroutine可以继续执行
             letter <- true
          }
       }
    }()

    // 将 WaitGroup 的计数器加1, 表示有一个goroutine需要等待
    wait.Add(1)

    // 启动一个goroutine, 用于打印字母
    go func(wait *sync.WaitGroup) {
       // 初始化一个变量i,用于记录当前打印的字母
       i := 'A'
       for {
          select {
          // 从letter channel中接收信号, 表示可以打印字母了
          case <-letter:
             // 如果已经打印完了所有的字母, 则调用WaitGroup的Done方法, 表示当前goroutine已经完成, 然后返回
             if i >= 'Z' {
                wait.Done()
                return
             }
             fmt.Print(string(i))
             i++
             fmt.Print(string(i))
             i++
             // 向number channel发送信号, 通知打印数字的goroutine可以继续执行
             number <- true
          }
       }
    }(&wait)

    // 向number channel发送信号, 表示可以开始打印数字了
    number <- true

    // 等待所有的goroutine完成
    wait.Wait()
}

一些值得讨论的细节:

1、这段代码中的两个 go goroutine 会交替执行,因为它们是通过无缓冲的 channel letter 和 number 进行通信,当一个 goroutine 打印完数字或字母后,它会通过相应的 channel 通知另一个 goroutine 继续执行。由于 main 函数中首先向 number channel 发送了一个信号,因此打印数字的 goroutine 会先开始执行。然后,打印字母的 goroutine 会在接收到 letter channel 的信号后开始执行。在打印字母的 goroutine 中,当变量 i 大于等于 'Z' 时,它会调用 wait.Done() 表示自己已经完成,然后返回。此时,wait.Wait() 会停止阻塞,main 函数会继续执行并结束。

2、在Go语言中,fmt.Print 函数接受一个或多个参数,并将这些参数打印到标准输出。当你直接将一个字符类型的值(如 i := 'A')传递给 fmt.Print 时,它会尝试将这个字符转换为其对应的整数值,然后打印这个整数值。这是因为在Go语言中,字符类型(rune)实际上是一个整数类型,它表示Unicode字符的代码点。

如果你想要打印字符本身而不是它的整数值,你需要将字符转换为字符串类型。这就是为什么 fmt.Print(string(i)) 能够正确打印字符的原因。在这个调用中,string(i) 将字符 i 转换为字符串类型,然后 fmt.Print 函数将字符串打印到控制台。

3、打印数字的 goroutine 在启动后会进入一个无限循环,在这个循环中它会不断地等待从 number 通道接收到信号。一旦接收到信号,它就会执行相应的操作,即打印两个连续的数字,然后向 letter 通道发送一个信号,通知打印字母的 goroutine 可以继续执行。

在这个过程中,打印数字的 goroutine 会一直阻塞在 select 语句的 case <-number: 分支上,直到 number 通道接收到数据。这是因为 number 通道是一个无缓冲的通道,这意味着如果没有其他 goroutine 向 number 通道发送数据,那么从 number 通道接收数据的操作就会一直阻塞。打印数字的 goroutine 会一直等待,直到它从 number 通道接收到信号。这个信号通常是由打印字母的 goroutine 在完成两个字母的打印后发送的。