interview-go icon indicating copy to clipboard operation
interview-go copied to clipboard

交替打印数字和字母 - 示例问题描述及优化

Open thinkeridea opened this issue 5 years ago • 1 comments

示例代码:

	letter,number := make(chan bool),make(chan bool)
	wait := sync.WaitGroup{}

	go func() {
		i := 1
		for {
			select {
			case <-number:
				fmt.Print(i)
				i++
				fmt.Print(i)
				i++
				letter <- true
				break
			default:
				break
			}
		}
	}()
	wait.Add(1)
	go func(wait *sync.WaitGroup) {
		str := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

		i := 0
		for{
			select {
			case <-letter:
				if i >= strings.Count(str,"")-1 {
					wait.Done()
					return
				}

				fmt.Print(str[i:i+1])
				i++
				if i >= strings.Count(str,"") {
					i = 0
				}
				fmt.Print(str[i:i+1])
				i++
				number <- true
				break
			default:
				break
			}

		}
	}(&wait)
	number<-true
	wait.Wait()

这个代码正确得到了结果,但是代码它本身存在严重的瑕疵:

  • (重要)接收信号使用了 select default 模式, chan 没有数据时,陷入多余的循环,浪费硬件资源
  • (重要)没有正确关闭 number 与 letter 通道,这个简短的程序它运行没有问题,是等待 main 函数退出系统回收相关资源的
  • (重要)打印 number 的 goroutine 没有正确关闭,是等待 main 函数退出系统回收
  • (次要)低效率的字典模式打印字符串,需要预先创建需要打印字符的字典,然后按字典进行打印

thinkeridea avatar Jul 30 '20 04:07 thinkeridea

交替打印数字和字母

问题描述

使用两个 goroutine 交替打印序列,一个 goroutine 打印数字, 另外一个 goroutine 打印字母, 最终效果如下:

12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ2728

考点

goroutine 状态控制,执行顺序控制, 对 chan 的应用, 以及正确的退出程序。

解题思路

问题很简单,使用 channel 来控制打印的进度。使用两个 channel ,来分别控制数字和字母的打印序列, 数字打印完成后通过 channel 通知字母打印, 字母打印完成后通知数字打印,然后周而复始的工作。

源码参考

package main

import (
	"fmt"
	"sync"
)

func main() {
	letter, number := make(chan bool), make(chan bool)
	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		i := 1
		for {
			next := <-number
			for j := 0; j < 2; j++ {
				fmt.Print(i)
				i++
			}
			if next {
				letter <- true
				continue
			}

			break
		}

		close(letter)
	}()

	go func() {
		defer wg.Done()
		l := 'A'
		for l <= 'Z' {
			<-letter
			for i := 0; i < 2 && l <= 'Z'; i++ {
				fmt.Print(string(l))
				l++
			}
			number <- l <= 'Z'
		}

		close(number)
	}()

	number <- true
	wg.Wait()
}

源码解析

  • 使用 letternumber 两个 chan 控制 goroutine 执行顺序,letter 控制打印字母, number 控制打印数字, 倒数第三行通知优先打印数字。
  • letter 停止打印是字母表控制的, 当打印完 Z 字母之后即终止, number 需要接受是否终止(退出)的事件,这个事件是由打印字母的 goroutine 控制的, 所以使用 chan bool 类型 , 为了保持一致性letter并没有采用 chan struct{} 类型,而是和 number 一致的 chan bool
  • 字母采用字节码计算, 可以很好的完成自增和边界检查。
  • 当字母打印完 Z 之母之后,会向 number 发送 false, 用来表示 letter 不在接收新的任务,并且打印任务也将结束, 并且关闭 number 通道。
  • number 接收到打印任务事件后,先打印数字,之后检查接收的 next 是否为 true, 如果是 true 则通知 letter 进行字母打印, 否者的话就退出循环,并且关闭 letter 通道。
  • 两个 goroutine 退出,wg.Wait() 结束,退出 main 函数,整个程序结束。

重点

  • 程序中使用的 chan 都是在写端关闭的,这是 chan 安全使用的最基本准则
  • 程序退出前正确的关闭所有的 changoroutine 相关资源

thinkeridea avatar Jul 30 '20 05:07 thinkeridea