关于并发代码的理解

52 阅读1分钟

GO语言相对于其他语言的最高特点就是其高并发性。通过go就可以实现一个函数的并发操作 下面用一个例子来解释说明

package main

import (
   "fmt"
   "math/rand"
   "time"
)

type Result string
type Search func(query string) Result

var (
   Web   = fakeSearch("web")
   Image = fakeSearch("image")
   Video = fakeSearch("video")
)

func fakeSearch(kind string) Search {
   return func(query string) Result {
      time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) // 将搜索时间更改为随机值
      return Result(fmt.Sprintf("%s result for %q\n", kind, query))
   }
}

func Google(query string) []Result {
   c := make(chan Result)

   go func() {
      c <- Web(query)
   }()
   go func() {
      c <- Image(query)
   }()
   go func() {
      c <- Video(query)
   }()

   var results []Result

   // 在这里,timeout 会在 50 毫秒后接收到管道传来的信息
   timeout := time.After(50 * time.Millisecond)
   for i := 0; i < 3; i++ {
      select {
      case r := <-c:
         results = append(results, r)
      case <-timeout: // 在 timeout 接收到信息后,将会结束搜索,并将结果直接返回
         fmt.Println("timeout")
         return results
      }
   }

   return results
}

func main() {
   rand.Seed(time.Now().UnixNano())
   start := time.Now()
   results := Google("golang")
   elapsed := time.Since(start)
   fmt.Println(results)
   fmt.Println(elapsed)
}

这个代码是来自于一个大佬的对于协程,管道以及select用法的文章 原链接:Golang 并发编程实战——协程、管道、select用法 - 掘金 (juejin.cn) 下面说一下我自己对于这个代码的理解。 因为对于三个函数的并发操作无法控制其优先级,所以将其全部存入到自定义的C通道中。

var (
   Web   = fakeSearch("web")
   Image = fakeSearch("image")
   Video = fakeSearch("video")
)

对于这个三个参数的返回值是 Search类型,而 Search是我们通过type定义的一个带有参数返回值为Result的函数。其实就是一个字符串类型,因为我们通过type将其定义为了string类型。所以上面这三个参数实际上就是一个含有string类型的参数且返回值也为string的函数。 这也就是为什么下面这函数的返回值为

func fakeSearch(kind string) Search {
   return func(query string) Result {
      time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) // 将搜索时间更改为随机值
      return Result(fmt.Sprintf("%s result for %q\n", kind, query))
   }
}
web result for "golang"
video result for "golang"
image result for "golang"

但是上面这个代码的运行结果不一定是这个,因为我们通过了时间函数对这个搜索进行了时间的随机性。 而我们在

// 在这里,timeout 会在 50 毫秒后接收到管道传来的信息
timeout := time.After(50 * time.Millisecond)
for i := 0; i < 3; i++ {
   select {
   case r := <-c:
      results = append(results, r)
   case <-timeout: // 在 timeout 接收到信息后,将会结束搜索,并将结果直接返回
      fmt.Println("timeout")
      return results
   }
}

这个地方对通道进行了一个设定,如果时间小于50ms则将其结果返回到通道C中,如果超过了50ms就显示timeout同时return results字符串数组返回。 最终在屏幕上显示的结果每一次也会不同,因为我们通过

rand.Seed(time.Now().UnixNano())

保证了我们的随机数种子不同。最后的结果可能为

[
 web result for "golang"
 video result for "golang"
 image result for "golang"
]
33.1508ms

也有可能为

timeout
[
 video result for "golang"
 web result for "golang"
]
63.5574ms

只有2个结果剩下一个超过了50ms没有传入通道,等等情况。