golang多線程之精巧

遇到一個(gè)數(shù)據(jù)查找問題,不算很難,但是使用golang的context包和goroutine相關(guān)內(nèi)容來實(shí)現(xiàn)令人無比舒適,下面看題目:

假設(shè)有一個(gè)超長的切片(其他語言的同學(xué)理解為數(shù)組即可),切片的元素類型為int,切片中的元素為亂序排列。限時(shí)5秒,使用多線程查找切片中是否存在給定值,在找到目標(biāo)值或者超時(shí)后立刻結(jié)束所有的查找任務(wù)。

下面來演示解決方法,首先進(jìn)行一點(diǎn)點(diǎn)背景介紹,go對多線程進(jìn)行了協(xié)程封裝(goroutine),goroutine間數(shù)據(jù)通訊建議使用通道(channel,一種可以有緩沖的隊(duì)列),context包提供了多線程上下文控制的能力,select是一個(gè)可以對通道通訊進(jìn)行switch的控制結(jié)構(gòu),通過這幾塊內(nèi)容的組合,可以優(yōu)雅精巧的實(shí)現(xiàn)題目的解決,下面直接看思路:

  1. 首先題目里提到了在找到目標(biāo)值或者超時(shí)后立刻結(jié)束所有g(shù)oroutine的執(zhí)行,完成這兩個(gè)功能需要借助計(jì)時(shí)器、通道和context才行。我能想到的第一點(diǎn)就是要用context.WithCancel創(chuàng)建一個(gè)上下文對象傳遞給每個(gè)執(zhí)行任務(wù)的goroutine,外部在滿足條件后(找到目標(biāo)值或者已超時(shí))通過調(diào)用上下文的取消函數(shù)來通知所有g(shù)oroutine停止工作。
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()
    }
}
  1. 執(zhí)行任務(wù)的goroutine們?nèi)绻业侥繕?biāo)值后需要通知外部等待任務(wù)執(zhí)行的主goroutine,這個(gè)工作是典型的應(yīng)用通道的場景,上面代碼也已經(jīng)看到了,我們創(chuàng)建了一個(gè)接收查找結(jié)果的通道,接下來要做的就是把它和上下文對象一起傳遞給執(zhí)行任務(wù)的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:
        }
        // 模擬一個(gè)耗時(shí)查找,這里只是比對值,真實(shí)開發(fā)中可以是其他操作
        fmt.Fprintf(os.Stdout, "v: %d \n", v)
        time.Sleep(time.Millisecond * 1500)
        if target == v {
            resultChan <- true
            return
        }
    }

}

3.在執(zhí)行查找任務(wù)的goroutine里接收上下文的取消信號,為了不阻塞查找任務(wù),我們使用了select語句加default的組合:

select {
case <- ctx.Done():
    fmt.Fprintf(os.Stdout, "Task cancelded! \n")
    return
default:
}

4.在goroutine里面如果找到了目標(biāo)值,則會(huì)通過發(fā)送一個(gè)true值給resultChan,讓外面等待的主goroutine收到一個(gè)已經(jīng)找到目標(biāo)值的信號。

resultChan <- true

5.這樣通過上下文的Done通道和resultChan通道,goroutine們就能相互通信了。

Go 語言中最常見的、也是經(jīng)常被人提及的設(shè)計(jì)模式 — 不要通過共享內(nèi)存的方式進(jìn)行通信,而是應(yīng)該通過通信的方式共享內(nèi)存

  1. 下面是完整代碼:
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:
        }
        // 模擬一個(gè)耗時(shí)查找,這里只是比對值,真實(shí)開發(fā)中可以是其他操作
        fmt.Fprintf(os.Stdout, "v: %d \n", v)
        time.Sleep(time.Millisecond * 1500)
        if target == v {
            resultChan <- true
            return
        }
    }

}

實(shí)在是太精妙了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。