遇到一個數據查找問題,不算很難,但是使用golang的context包和goroutine相關內容來實現令人無比舒適,下面看題目:
假設有一個超長的切片(其他語言的同學理解為數組即可),切片的元素類型為int,切片中的元素為亂序排列。限時5秒,使用多線程查找切片中是否存在給定值,在找到目標值或者超時后立刻結束所有的查找任務。
下面來演示解決方法,首先進行一點點背景介紹,go對多線程進行了協程封裝(goroutine),goroutine間數據通訊建議使用通道(channel,一種可以有緩沖的隊列),context包提供了多線程上下文控制的能力,select是一個可以對通道通訊進行switch的控制結構,通過這幾塊內容的組合,可以優雅精巧的實現題目的解決,下面直接看思路:
- 首先題目里提到了在找到目標值或者超時后立刻結束所有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")
return
default:
}
// 模擬一個耗時查找,這里只是比對值,真實開發中可以是其他操作
fmt.Fprintf(os.Stdout, "v: %d \n", v)
time.Sleep(time.Millisecond * 1500)
if target == v {
resultChan <- true
return
}
}
}
3.在執行查找任務的goroutine里接收上下文的取消信號,為了不阻塞查找任務,我們使用了select語句加default的組合:
select {
case <- ctx.Done():
fmt.Fprintf(os.Stdout, "Task cancelded! \n")
return
default:
}
4.在goroutine里面如果找到了目標值,則會通過發送一個true值給resultChan,讓外面等待的主goroutine收到一個已經找到目標值的信號。
resultChan <- true
5.這樣通過上下文的Done通道和resultChan通道,goroutine們就能相互通信了。
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()
}
}
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
}
}
}
實在是太精妙了。