Go语言中的管道(channel)

828 阅读3分钟

管道(channel)

管道(channel)是 Go 语言中实现并发的一种方式,它可以在多个 goroutine 之间进行通信和数据交换。管道可以看做是一个队列,通过它可以进行先进先出的数据传输,支持并发的读和写。

Go 语言中使用 make 函数来创建一个管道,它的语法如下:

ch := make(chan 数据类型)

其中,数据类型可以是任意的 Go 语言数据类型,例如 int、string 等。创建了一个管道之后,我们就可以在多个 goroutine 之间进行数据传输了。

无缓冲管道

无缓冲管道是指在创建管道时没有指定容量,也就是说,它只能存储一个元素,当一个 goroutine 尝试向管道发送数据时,它会阻塞直到另一个 goroutine 从管道中读取数据。同样的,当一个 goroutine 尝试从管道中读取数据时,它也会阻塞直到另一个 goroutine 向管道中发送数据。

示例代码

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int)
    go func() {
        ch <- 10
    }()
    fmt.Println(<-ch)
}

在这个例子中,我们创建了一个无缓冲管道 ch,并在一个 goroutine 中向管道中发送了一个整数 10。在主 goroutine 中,我们通过 <-ch 从管道中读取数据并打印出来。

有缓冲管道

有缓冲管道是指在创建管道时指定了容量,这时候它可以存储多个元素,但是当管道已满时,尝试向管道发送数据的 goroutine 会被阻塞,直到另一个 goroutine 从管道中读取数据以腾出空间。同样的,当管道为空时,尝试从管道中读取数据的 goroutine 也会被阻塞,直到另一个 goroutine 向管道中发送数据。

示例代码

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 2)
    ch <- 10
    ch <- 20
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

在这个例子中,我们创建了一个容量为 2 的有缓冲管道 ch,并在主 goroutine 中向管道中依次发送了整数 1020。接着,我们依次从管道中读取数据并打印出来。

需要注意

  1. 管道是有缓冲的,可以通过指定缓冲区大小来控制数据在管道中的流动。如果缓冲区已满,写入操作将会阻塞直到缓冲区有空间;如果缓冲区为空,读取操作将会阻塞直到有数据写入。
  2. 管道的写入和读取操作都是阻塞的,直到操作完成才会返回。如果需要非阻塞的读写操作,可以使用select语句进行多路复用。
  3. 管道可以被关闭,一旦管道被关闭,读取操作将不再阻塞,返回一个零值和一个标识管道已关闭的错误;写入操作将会抛出 panic。为了避免 panic,可以在写入操作之前先检查管道是否已关闭。
  4. 管道可以用作信号量或同步器,例如使用一个无缓冲的管道实现多个 goroutine 之间的同步。

goroutine与channel实现并发

下面是一个协程与管道实现并发的代码示例,其中使用了两个管道,一个用于发送整数数据,另一个用于接收处理后的数据:

package main

import (
	"fmt"
)

func produce(out chan<- int) {
	for i := 0; i < 5; i++ {
		out <- i
	}
	close(out)
}

func consume(in <-chan int, out chan<- int) {
	for v := range in {
		out <- v * v
	}
	close(out)
}

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go produce(ch1)
	go consume(ch1, ch2)

	for v := range ch2 {
		fmt.Println(v)
	}
}

代码分析

  1. 使用 make 函数创建了两个整数类型的管道 ch1ch2
  2. 使用 go 关键字分别启动了函数 produceconsume 的协程,其中函数 produce 向管道 ch1 中发送了整数数据,函数 consume 从管道 ch1 中接收数据进行处理,将处理结果发送到管道 ch2 中。
  3. 在主协程中,使用 range 关键字从管道 ch2 中循环接收处理结果,并将接收到的数据打印出来。