# Golang让协程交替输出

90design · · 4089 次点击 · · 开始浏览

What you are wasting today is tomorrow for those who died yesterday; what you hate now is the future you can not go back.

之前用Golang写过一篇关于下载的文章（https://my.oschina.net/90design/blog/1607131）， 最后提到：如果新需求是同时下载，并且按循序下载，最近看到在论坛里有人又再问起，就想起来更新一下此问题。

# 开始

1. ## 两个协程交替输出1-20

``````package main

import "fmt"

func main() {
A := make(chan bool, 1)
B := make(chan bool)
Exit := 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() {
defer func() {
close(Exit)
}()
for i := 1; i <= 20; i++ {
if ok := <-B; ok {
fmt.Println("B : ", 2*i)
A <- true
}
}
}()

A <- true
<-Exit
}
``````

## 扩展写法

``````func main() {
ch := make(chan int)
exit := make(chan struct{})

go func() {
for i := 1; i <= 20; i++ {
println("g1:", <-ch)  // 执行步骤1， 执行步骤5
i++  //执行步骤6
ch <- i // 执行步骤7
}
}()

go func() {
defer func() {
close(ch)
close(exit)
}()
for i := 0; i < 20; i++ {
i++  // 执行步骤2
ch <- i  //执行步骤3
println("g2:", <-ch) //执行步骤4
}
}()

<-exit
}``````

# 问题延伸

多个协程，按一定顺序执行任务

``````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
}
}
}
``````

### 解释：

以上代码通过多个协程建立多个方法的方式完成多协程的执行任务， 你可能会问了： 如果100个协程还要有100个对应的方法？ 答案是： 肯定 ， 不可能啊， 立马来个优化方案。

## 优化方案：

``````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
}
}
}
``````

### 解释：

可以说是通过递归的方式，通过一个协程执行完成再来生成第二个协程的方式， 本人是通过轮训channels的slice来完成协程之间的同步任务， 如果还有更好的方式请留言哦。 就算你有100W个也不惧怕， 看你配置了。

# 结语

1 回复  |  直到 2021-03-12 10:29:43

• 请尽量让自己的回复能够对别人有帮助
• 支持 Markdown 格式, **粗体**、~~删除线~~、``单行代码``
• 支持 @ 本站用户；支持表情（输入 : 提示），见 Emoji cheat sheet
• 图片支持拖拽、截图粘贴等方式上传

What you are wasting today is tomorrow for those who died yesterday; what you hate now is the future you can not go back.

之前用Golang写过一篇关于下载的文章（https://my.oschina.net/90design/blog/1607131）， 最后提到：如果新需求是同时下载，并且按循序下载，最近看到在论坛里有人又再问起，就想起来更新一下此问题。

# 开始

1. ## 两个协程交替输出1-20

``````package main

import "fmt"

func main() {
A := make(chan bool, 1)
B := make(chan bool)
Exit := 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() {
defer func() {
close(Exit)
}()
for i := 1; i <= 20; i++ {
if ok := <-B; ok {
fmt.Println("B : ", 2*i)
A <- true
}
}
}()

A <- true
<-Exit
}
``````

## 扩展写法

``````func main() {
ch := make(chan int)
exit := make(chan struct{})

go func() {
for i := 1; i <= 20; i++ {
println("g1:", <-ch)  // 执行步骤1， 执行步骤5
i++  //执行步骤6
ch <- i // 执行步骤7
}
}()

go func() {
defer func() {
close(ch)
close(exit)
}()
for i := 0; i < 20; i++ {
i++  // 执行步骤2
ch <- i  //执行步骤3
println("g2:", <-ch) //执行步骤4
}
}()

<-exit
}``````

# 问题延伸

多个协程，按一定顺序执行任务

``````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
}
}
}
``````

### 解释：

以上代码通过多个协程建立多个方法的方式完成多协程的执行任务， 你可能会问了： 如果100个协程还要有100个对应的方法？ 答案是： 肯定 ， 不可能啊， 立马来个优化方案。

## 优化方案：

``````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
}
}
}
``````

### 解释：

可以说是通过递归的方式，通过一个协程执行完成再来生成第二个协程的方式， 本人是通过轮训channels的slice来完成协程之间的同步任务， 如果还有更好的方式请留言哦。 就算你有100W个也不惧怕， 看你配置了。