本文共 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/