Go channel 创建模式

0 阅读1分钟

在稍复杂一些的并发程序中,需要考虑通过 CSP 模型输入/输出原语的承载体 channel 在 goroutine 之间建立联系。

为了满足这一需求,通常使用下面的方式来创建一个 goroutine:

type T struct{ ... }

func spawn(f func()chan T {
 c := make(chan T)
 go func() {
  // 使用channel变量c(通过闭包方式)与调用spawn的goroutine通信
  ... ...
  f()
  ... ...
 }()
 return c
}

func main() {
 c := spawn(func() {})
 // 使用channel变量c与新创建的goroutine通信
}

这个在内部创建一个 goroutine 并返回一个 channel 类型变量的函数就是 Go 中最常见的 goroutine 创建模式。

spawn 函数创建的新 goroutine 与调用 spawn 函数的 goroutine 之间通过一个 channel 建立起了联系:两个 goroutine 可以通过这个 channel 进行通信。

e.g.

生成一个数据流:

func spawn(start, count int) <-chan int {
 c := make(chan int)
 go func() {
  defer close(c)
  for i := start; i < start+count; i++ {
   c <- i
  }
 }()
 return c
}

e.g.

获取数据流中的前 n 个数据:

func spawn(ctx context.Context, values ...any) <-chan any {
 c := make(chan any)
 go func() {
  defer close(c)
  for _, v := range values {
   select {
   case <-ctx.Done():
    return
   case c <- v:
   }
  }
 }()
 return c
}

func takeN(ctx context.Context, in <-chan any, num int) <-chan any {
 c := make(chan any)
 go func() {
  defer close(c)
  for i := 0; i < num; i++ {
   select {
   case <-ctx.Done():
    return
   case v, ok := <-in: // 从输入流中读取元素
    if !ok {
     return
    }
    c <- v
   }
  }
 }()
 return c
}

func main() {
 ctx, cancel := context.WithCancel(context.Background())
 defer cancel()
 // 创建流
 in := spawn(ctx, []any{12345}...)
 // 只取流中的前n个数据
 out := takeN(ctx, in, 3)
 for v := range out {
  fmt.Println(v)
 }
}

// Output:
// 1
// 2
// 3