并发问题的解决思路以及Go语言调度器工作原理

今天的文章我首先说一下之前文章里的思考题的解决思路 , 我会给出完整可运行的代码 。之后通过观察程序的运行结果里的现象简单介绍 Go 语言的调度器是如何对 goroutine 进行调度的 。
回答之前的问题先来回顾一下上周文章里思考题的题目:
假设有一个超长的切片 , 切片的元素类型为int , 切片中的元素为乱序排列 。限时5秒 , 使用多个goroutine查找切片中是否存在给定值 , 在找到目标值或者超时后立刻结束所有goroutine的执行 。
比如切片为:[23, 32, 78, 43, 76, 65, 345, 762, ...... 915, 86] , 查找的目标值为345 , 如果切片中存在目标值程序输出:"Found it!"并且立即取消仍在执行查找任务的 goroutine。如果在超时时间未找到目标值程序输出:"Timeout! Not Found" , 同时立即取消仍在执行查找任务的 goroutine。
首先题目里提到了 在找到目标值或者超时后立刻结束所有goroutine的执行  , 完成这两个功能需要借助计时器、通道和 context 才行 。我能想到的第一点就是要用 context.WithCancel 创建一个上下文对象传递给每个执行任务的 goroutine  , 外部在满足条件后(找到目标值或者已超时)通过调用上下文的取消函数来通知所有 goroutine 停止工作 。
func main() {timer := time.NewTimer(time.Second * 5)ctx, cancel := context.WithCancel(context.Background())resultChan := make(chan bool)......select {case <-timer.C:fmt.Fprintln(os.Stderr, "Timeout! Not Found")cancel()case <- resultChan:fmt.Fprintf(os.Stdout, "Found it!n")cancel()}}执行任务的 goroutine 们如果找到目标值后需要通知外部等待任务执行的主 goroutine  , 这个工作是典型的应用通道的场景 , 上面代码也已经看到了 , 我们创建了一个接收查找结果的通道 , 接下来要做的就是把它和上下文对象一起传递给执行任务的 goroutine。
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")returndefault:}// 模拟一个耗时查找 , 这里只是比对值 , 真实开发中可以是其他操作fmt.Fprintf(os.Stdout, "v: %d n", v)time.Sleep(time.Millisecond * 1500)if target == v {resultChan <- truereturn}}}在执行查找任务的 goroutine 里接收上下文的取消信号 , 为了不阻塞查找任务 , 我们使用了 select 语句加 default 的组合:
select {case <- ctx.Done():fmt.Fprintf(os.Stdout, "Task cancelded! n")returndefault:}在 goroutine 里面如果找到了目标值 , 则会通过发送一个 true 值给 resultChan  , 让外面等待的主 goroutine 收到一个已经找到目标值的信号 。
resultChan <- true这样通过上下文的 Done 通道和 resultChan 通道 ,  goroutine 们就能相互通信了 。
Go 语言中最常见的、也是经常被人提及的设计模式 — 不要通过共享内存的方式进行通信 , 而是应该通过通信的方式共享内存
完整的源代码如下:
package mainimport ("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 := 3target := 345ctx, cancel := context.WithCancel(context.Background())resultChan := make(chan bool)for i := 0; i < dataLen; i += size {end := i + sizeif 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")returndefault:}// 模拟一个耗时查找 , 这里只是比对值 , 真实开发中可以是其他操作fmt.Fprintf(os.Stdout, "v: %d n", v)time.Sleep(time.Millisecond * 1500)if target == v {resultChan <- truereturn}}}为了打印演示结果所以加了几处 time.Sleep  , 这个程序更多的是提供思路框架 , 所以细节的地方没有考虑 。有几位读者把他们的答案发给了我 , 其中有一位的提供的答案在代码实现上考虑的更全面 , 这个我们放到文末再说 。
上面程序的执行结果如下:


推荐阅读