练习题-golang交替打印数字的各种情况

2021/03/03 golang
如果文章图片无法显示,可尝试配置host:修复Github图片资源无法显示问题

1、两个协程交替打印1-20

思路一

  • 创建两个协程,一个协程打印奇数,一个协程打印偶数
  • 使用一个无缓冲channel控制两个协程的执行顺序
package main

import (
	"fmt"
	"time"
)

// golang 两个协程交替打印1-20
// 使用无缓冲channel实现

func main() {
	ch := make(chan int)

	go func() {
		for i := 0; i < 20; i++ {
			ch <- 0
			if i%2 == 0 {
				fmt.Println(i)
			}
		}
	}()

	go func() {
		for i := 0; i < 20; i++ {
			<-ch
			if i%2 == 1 {
				fmt.Println(i)
			}
		}
	}()
	time.Sleep(time.Second *3)
}

思路二

  • 创建两个channel
package main

import "fmt"

func main() {
	A := make(chan bool, 1)
	B := make(chan bool)

	go func() {
		for i := 1; i <= 20; i++ {
			if ok := <-A; ok {
				fmt.Println("A = ", 2*i-1)
				B <- true
			}
		}
	}()
	go func() {
		for i := 1; i <= 20; i++ {
			if ok := <-B; ok {
				fmt.Println("B : ", 2*i)
				A <- true
			}
		}
	}()
	A <- true
    time.Sleep(time.Second *3)
}

思路三

可以在思路一上进行拓展,同样是创建两个channel,可以把需要输出的变量放到一个channel中,有一个函数专门用于打印该channel中的值

2、多个协程打印

思路1

通过多个协程建立多个方法的方式完成多协程的执行任务

package main

import (
	"fmt"
	"io"
)

var (
	num int
	A    = make(chan int)
	B    = make(chan int)
	C    = make(chan int)
	D    = make(chan int)
	exit = make(chan bool)
)

func main() {

	// 开启多协程
	go Aa()
	go Bb()
	go Cc()
	go Dd()

	// 接收要输出的最大数
	fmt.Println("输入要输出的最大数值:")
	_, ok := fmt.Scanf("%d\n", &num)
	if ok == io.EOF{
		return
	}
	// 触发协程同步执行
	A <- 1

	// 执行结束
	if <-exit{
		return
	}
}
func Aa() {
	for {
		if count := <-A;  count <= num {
			fmt.Println("A -> ", count)
			count++
			B <- count
		}else{
			fmt.Println("在通道D执行完成")
			close(exit)
			return
		}
	}
}
func Bb() {
	for {
		if count := <-B;  count <= num {
			fmt.Println("B -> ", count)
			count++
			C <- count
		}else{
			fmt.Println("在通道A执行完成")
			close(exit)
			return
		}
	}
}
func Cc() {
	for {
		if count := <-C; count <= num {
			fmt.Println("C -> ", count)
			count++
			D <- count
		}else{
			fmt.Println("在通道B执行完成")
			close(exit)
			return
		}
	}
}

func Dd() {
	for {
		if count, ok := <-D; ok && count <= num {
			fmt.Println("D -> ", count)
			count++
			A <- count
		}else{
			fmt.Println("在通道C执行完成")
			close(exit)
			return
		}
	}
}

优化

如上代码需要我们对每一个协程编写对应的方法,如果协程数量非常多,如上代码肯定不利于代码的维护,因此需要进行优化

不过该方法有些问题:”go ChanWork(chans[line])” 这句会不断创建goroutine,并不满足定goroutine的写法,后期我们会对其进行优化。

package main

import (
	"fmt"
	"io"
	"strconv"
)

var (
	num   int // 要输出的最大值
	line  = 0 // 通道发送计数器
	exit  = make(chan bool)
	chans []chan int // 要初始化的协程数量
)

func main() {
	// 开启4个协程
	chans = []chan int{
		make(chan int),
		make(chan int),
		make(chan int),
		make(chan int) }

	// 多协程启动入口
	go ChanWork(chans[0])

	// 接收要输出的最大数
	fmt.Println("输入要输出的最大数值:")
	_, ok := fmt.Scanf("%d\n", &num)
	if ok == io.EOF {
		return
	}
	// 触发协程同步执行
	chans[0] <- 1

	// 执行结束
	if <-exit {
		return
	}
}

func ChanWork(c chan int) {
	// 协程数
	lens := len(chans)
	for {
		// count为输出计数器
		if count := <-chans[line]; count <= num {
			fmt.Println("channel "+strconv.Itoa(line)+" -> ", count)
			count++

			// 下一个发送通道
			line++
			if line >= lens {
				line = 0 //循环,防止索引越界
			}
			go ChanWork(chans[line])
			chans[line] <- count

		} else {
            // 通道编号问题处理
			id := 0
			if line == 0{
				id = lens-1
			}else{
				id = line-1
			}
			fmt.Println("在通道" + strconv.Itoa(id) + "执行完成")
			close(exit)
			return
		}
	}
}

进一步优化:

package main

import "fmt"

func printNum(maxNum int, chanPre chan int, chanNext chan int, exit chan bool, id string) {
    for i := 0; i <= maxNum; i++ {
        num := <-chanPre
        select {
        case <-exit:
            return
        default:
            if num == maxNum+1 {
                exit <- true
            } else {
                fmt.Printf("id %s:%d\n", id, num)
                num += 1
                chanNext <- num
            }
        }
    }
}

func main() {
    
    exit := make(chan bool)
    maxNum := 20
    
    chanA := make(chan int)
    chanB := make(chan int)
    chanC := make(chan int)
    
    go printNum(maxNum, chanA, chanB, exit, "A")
    go printNum(maxNum, chanB, chanC, exit, "B")
    go printNum(maxNum, chanC, chanA, exit, "C")
    
    chanA <- 0
    <-exit
}

我们仍然是先写一个不太完善的例子,这个例子虽然实现了多个协程交替打印并将三个打印函数合并成为了一个,但是对于channel的声明,仍然需要我们手动,声明,接下来我们会对这一块进行优化

完善后:

package main

import (
    "fmt"
)

func printNum(maxNum int, chanPre chan int, chanNext chan int, exit chan bool, id string) {
    for i := 0; i <= maxNum; i++ {
        num := <-chanPre
        select {
        case <-exit:
            return
        default:
            if num == maxNum+1 {
                exit <- true
            } else {
                fmt.Printf("id %s:%d\n", id, num)
                num += 1
                chanNext <- num
            }
        }
    }
}

func main() {
    
    exit := make(chan bool)
    maxNum := 20
    
    gNum := 5
    if gNum < 2 {
        return
    }
    chanArr := make([]chan int, gNum)
    
    for i := 0; i < gNum; i++ {
        chanArr[i] = make(chan int)
    }
    
    for i := 0; i < gNum; i++ {
        if i != len(chanArr)-1 {
            go printNum(maxNum, chanArr[i], chanArr[i+1], exit, string(rune(i)+'A'))
        } else {
            go printNum(maxNum, chanArr[i], chanArr[0], exit, string(rune(i)+'A'))
        }
        
    }
    chanArr[0] <- 0
    <-exit
}

参考资料


公众号:豆仔gogo

golang、算法、后端知识、面试手册

豆仔gogo

Search

    公众号:豆仔gogo

    豆仔gogo

    Post Directory