Golang让协程交替输出

90design · · 2072 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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
}

解释:

首先给通道A一个缓存,并在主进程中发送数据,使其堵塞,在第一个Goroutine中通道A接收并开始执行, 此时B是堵塞等待的, 等A执行完成发送数据到通道B, B开始执行。

 

扩展写法

不过在论坛看到其他人码友直接使用一个输出通道搞定,贴出来大家看看:

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
}

 

问题延伸

问题延伸出来, 如果 >2 个协程呢?

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

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个也不惧怕, 看你配置了。

 

结语

就写到这里吧,详细的优化后期再完成, 细嚼才能慢咽啊!

建议很重要,交流进步才是硬道理啊!

 

 

 

 


有疑问加站长微信联系(非本文作者)

本文来自:开源中国博客

感谢作者:90design

查看原文:Golang让协程交替输出

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:1006366459

2072 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传