博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Go 实现两个goroutine交替打印切片并实现超时控制
阅读量:4189 次
发布时间:2019-05-26

本文共 4760 字,大约阅读时间需要 15 分钟。

目录


 

题目描述

请用代码实现一个函数:该函数接收一个整型类型的切片作为参数,函数体使用两个goroutines交替打印切片中的元素。该函数还需要一个参数实现超时控制,在指定的时间后,若切片还没有打印完,要求停止两个goroutines和该函数的执行。

 

解决方案

对于超时问题,我们可以使用context包中的WithTimeout函数来约束:

ctx, cancel := context.WithTimeout(context.Background(), time.Second)defer cancel()for {    switch {    case <- ctx.Done():        // timeout...    default:       // ...    }}

 

对于交替打印,为了避免两个goroutine打印同一个值,可以让一个goroutine打印切片索引为奇数的值,另一个goroutine打印切片索引为偶数的值。仅仅区分开两个goroutine打印的值还不够,因为不同的goroutine打印的速度不一定相同,因此不能严格的实现“交替打印”。因此需要用一个channel来控制goroutines,让一个goroutine打印完后等待另一个goroutine打印,然后再执行下一轮打印步骤。

 

实现

题目的目的是实现一个函数,其有两个参数,一个是要打印的切片,另一个是超时时间。为了测试覆盖到更多的场景(比如大长度切片),我们总不能手写一个固定长度的切片去验证我们后续的逻辑,因此,需要写一个生成指定长度的切片的函数:

func GenerateList(min, max int) []int {	result := make([]int, 0)	for i := min; i < max; i++ {		result = append(result, i)	}	return result}

函数GenerateList生成一个数值连续的切片,它有两个参数,它表示生成的切片的数值范围。min表示切片的最小值,max表示切片的最大值 - 1。

下一步,根据题目的需要,我们需要定义一个函数,用来交替打印切片并实现超时控制,因此该函数要接收两个参数,一个待打印的切片,一个超时时间:

func PrintArray(list []int, timeout float64) {}

我们继续往下走。接下来考虑超时问题:

func PrintArray(list []int, timeout float64) {	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))	defer cancel()	for {		select {		case <- ctx.Done():			fmt.Println("timeout...")			return		}	}}

上面的代码中,我们设置了超时时间,并在函数末尾使用一个无限循环来检查是否超时。一旦超时,从cex.Done管道取出消息将不会被阻塞,因此执行return,函数结束执行。设置好了超时退出后,我们接下来继续创建两个goutinue,用来打印切片。我们让第一个goroutine打印切片的奇数索引对应的值,让第二个goroutine打印偶数索引对应的值:

func PrintArray(list []int, timeout float64) {	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))	defer cancel()	go func() {		for i := 0; i < len(list); i++ {			if i % 2 == 0 {				fmt.Println("goroutine-1: ", list[i])			}		}	} ()	go func() {		for i := 0; i < len(list); i++ {			if i % 2 == 1 {				fmt.Println("goroutine-2: ", list[i])			}		}	} ()	for {		select {		case <- ctx.Done():			fmt.Println("timeout...")			return		}	}}

看起来不错。但是这样的代码依然达不到要求:两个goroutine依然不是交替打印,有的时候第一个goroutine会比第二个goroutine打印的快,而有的时候第二个比第一个快。两个goroutine各跑各的,不受控制,这显然不是我们期望的样子。为了让两个goroutine配合协调,其中一个打印完要等等另一个,因此我们需要添加一个channel来控制这两个goroutine:

func PrintArray(list []int, timeout float64) {	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))	defer cancel()	allow := make(chan bool)	go func() {		for i := 0; i < len(list); i++ {			allow <- true			if i % 2 == 0 {				fmt.Println("goroutine-1: ", list[i])			}		}	} ()	go func() {		for i := 0; i < len(list); i++ {			<- allow			if i % 2 == 1 {				fmt.Println("goroutine-2: ", list[i])			}		}	} ()	for {		select {		case <- ctx.Done():			fmt.Println("timeout...")			return		}	}}

allow管道控制了两个goroutine循环的同步:当两个goroutine进入循环后,始终保证两个goroutine循环的i值相同。这样通过奇数和偶数的判断,最终每一次循环中只有一个goroutine可以打印。其实到这里,我们的代码已经完全实现了题目要求的所有内容,交替打印,超时退出。但是有一个十分不友好的bug:如果切片的长度很短,而超时时间设置的很长,那么select会一直等待超时结束,我们的程序虽然早早的交替打印完毕也不能退出,只能等待超时触发。为了让程序再打印结束后提前退出,我们需要引入一个打印完毕的判断:

func PrintArray(list []int, timeout float64) {	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))	defer cancel()	allow := make(chan bool)	finish := make(chan string)	finishedCount := 0	go func() {		for i := 0; i < len(list); i++ {			allow <- true			if i % 2 == 0 {				fmt.Println("goroutine-1: ", list[i])			}		}		finish <- "goroutine-1"	} ()	go func() {		for i := 0; i < len(list); i++ {			<- allow			if i % 2 == 1 {				fmt.Println("goroutine-2: ", list[i])			}		}		finish <- "goroutine-2"	} ()	for {		select {		case <- ctx.Done():			fmt.Println("timeout...")			return		case <- finish:			finishedCount += 1			if finishedCount == 2 {				return			}		}	}}

这次改动新增了finish管道和finishCount变量。再每一个goroutine正常打印完毕后,都向finish管道写入数据,而函数末尾的select语句中,会接收finish管道,一旦有数据取出,就给初始值为0的finishedCount自增1。当finishedCount值为2时,程序退出,不需要再等到超时。

 

完整代码

package mainimport (	"context"	"fmt"	"time")func PrintArray(list []int, timeout float64) {	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))	defer cancel()	allow := make(chan bool)	finish := make(chan string)	finishedCount := 0	go func() {		for i := 0; i < len(list); i++ {			allow <- true			if i % 2 == 0 {				fmt.Println("goroutine-1: ", list[i])			}		}		finish <- "goroutine-1"	} ()	go func() {		for i := 0; i < len(list); i++ {			<- allow			if i % 2 == 1 {				fmt.Println("goroutine-2: ", list[i])			}		}		finish <- "goroutine-2"	} ()	for {		select {		case <- ctx.Done():			fmt.Println("timeout...")			return		case <- finish:			finishedCount += 1			if finishedCount == 2 {				return			}		}	}}func GenerateList(min, max int) []int {	result := make([]int, 0)	for i := min; i < max; i++ {		result = append(result, i)	}	return result}func main() {	demo := GenerateList(0, 5)	PrintArray(demo, 1.1)}

修改main函数中的GenerateList参数值可以调整切片的长度,调整PrintArray函数的timeout参数可以调整超时时间。

 

传送门

转载地址:http://ijsoi.baihongyu.com/

你可能感兴趣的文章
积累20180203
查看>>
MySQL里获取当前week、month、quarter的start_date/end_date
查看>>
Mysql中DATE_SUB 使用方法结合查询一天内,一周内,一月内的信息实例讲解
查看>>
异构数据源海量数据交换工具-Taobao DataX 下载和使用
查看>>
代理模式解析,静态代理、动态代理一文全都告诉你
查看>>
我是如何从电脑小白走上编程之路
查看>>
想成为优秀的Java程序员,你需要读哪些书?
查看>>
Java并发| Atomic包下的原子操作类使用与原理解析
查看>>
Mac M1 安装 iTerm2+Oh My Zsh+zsh-syntax-highlighting 真香!
查看>>
M1芯片Mac 安装git
查看>>
M1芯片Mac Homebrew 安装
查看>>
一篇文章看懂ZooKeeper内部原理
查看>>
全面理解Java内存模型
查看>>
Java类型信息详解
查看>>
深入理解Java线程池
查看>>
Java线程堆栈分析
查看>>
Java中子类能否继承父类的私有属性和方法
查看>>
JVM内存模型详解
查看>>
(二)Git--工作区和暂存区、管理修改与撤销
查看>>
(七)Git--自定义Git
查看>>