您的位置 首页 golang

golang多线程之精巧

遇到一个数据查找问题,不算很难,但是使用golang的context包和goroutine相关内容来实现令人无比舒适,下面看题目:

假设有一个超长的切片(其他语言的同学理解为数组即可),切片的元素类型为int,切片中的元素为乱序排列。限时5秒,使用多线程查找切片中是否存在给定值,在找到目标值或者超时后立刻结束所有的查找任务。

下面来演示解决方法,首先进行一点点背景介绍,go对多线程进行了协程封装(goroutine),goroutine间数据通讯建议使用通道(channel,一种可以有缓冲的队列),context包提供了多线程上下文控制的能力,select是一个可以对通道通讯进行switch的控制结构,通过这几块内容的组合,可以优雅精巧的实现题目的解决,下面直接看思路:

  1. 首先题目里提到了在找到目标值或者超时后立刻结束所有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()    }}
  1. 执行任务的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")    returndefault:}

4.在goroutine里面如果找到了目标值,则会通过发送一个true值给resultChan,让外面等待的主goroutine收到一个已经找到目标值的信号。

resultChan <- true

5.这样通过上下文的Done通道和resultChan通道,goroutine们就能相互通信了。

Go 语言中最常见的、也是经常被人提及的设计模式 — 不要通过共享内存的方式进行通信,而是应该通过通信的方式共享内存

  1. 下面是完整代码:
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 := 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        }    }}

实在是太精妙了。


文章来源:智云一二三科技

文章标题:golang多线程之精巧

文章地址:https://www.zhihuclub.com/5994.shtml

关于作者: 智云科技

热门文章

网站地图