题目:假设有一超长切片,元素类型为int,切片中的元素为乱序排列,现需查找切片中是否存在某个元素。
要求:
-
5秒内不管有没有查询到该值,都需停止正在运行的程序。
-
使用多个goroutine来查找切片中是否存在该元素。
-
找到该元素或超时后需立刻停止所有goroutine的执行。
举例:设切片为[1,3,4,2,6,7,23,122...34,545,2321,4546,66],查找目标为123,如果切片中存在目标值,则程序输出【found it】,并立刻停止所有正在执行查找任务的goroutine,反之超时或是没找到该目标值,则输出【not found】,同理停止所有正在执行查找任务的goroutine。
解析:上述题目反复提到一个要求就是第三点【找到该元素或超时后需立刻停止所有goroutine的执行】,需使用channel、计时器、context来实现这个功能。
先使用context.WithChannel创建一个上下文对象传递给每个执行任务的goroutine,外部在找到目标值或程序超时的时候通过调用上下文的取消函数来通知所有执行任务的goroutine来停止工作。
执行任务的goroutine如果找到目标值后,需要通知外部等待执行的主goroutine,这个工作是典型的channel应用场景,可创建一个接收查找结果值的channel,之后就是把它和上下文对象一起传递给正在执行任务的goroutine。
可在执行查找任务的goroutine里接受上下文的取消信号,为不阻塞查找任务,可使用select语句加上default的组合。
拓展下,Go中尽量不要使用共享内存的方式通信,尽量使用通信的方式共享内存。
实现代码如下:
package main
import (
"context"
"fmt"
"os"
"time"
)
func main() {
timer := time.NewTimer(time.Second * 5)
data := []int{1, 2, 3, 10, 999, 8, 345, 7, 98, 33, 66, 77, 88, 68, 96}
dataLen := len(data)
size := 3
target := 345
ctx, cancel := context.WithCancel(context.Background())
resultChan := make(chan bool)
for i := 0; i < dataLen; i += size {
end := i + size
if end >= dataLen {
end = dataLen - 1
}
go SearchTarget(ctx, data[i:end], target, resultChan)
}
select {
case <-timer.C:
fmt.Fprintln(os.Stderr, "Timeout! Not Found")
cancel()
case <-resultChan:
fmt.Fprintf(os.Stdout, "Found it!\n")
cancel()
}
time.Sleep(time.Second * 2)
}
func SearchTarget(ctx context.Context, data []int, target int, resultChan chan bool) {
for _, v := range data {
select {
case <-ctx.Done():
fmt.Fprintf(os.Stdout, "Task cancelded! \n")
return
default:
}
// 模拟一个耗时查找,这里只是比对值,真实开发中可以是其他操作
fmt.Fprintf(os.Stdout, "v: %d \n", v)
time.Sleep(time.Millisecond * 1500)
if target == v {
resultChan <- true
return
}
}
}
输出结果如下:
上述代码中,goroutine里如果找到目标值,则会发送一个true给resultChan,让外面等待的主goroutine收到一个已找到目标值的信号,这样一来,通过上下文的Done通道和resultChan通道,各个goroutine之间就可以互相通信了。
扫码关注公众号,获取更多优质内容。