1. Channels 概念回顾
在 Golang 语言中,Channels 是一种类型,它允许不同的 Goroutines (Go 协程) 之间进行通信和同步。Channels 是一种基于消息传递的并发模型,可以让应用程序的各个组件在独立的 Goroutines 中运行,并通过 Channels 进行通信协作。
在 Golang 中创建 Channel 需要使用内置函数 make(),语法如下:
ch := make(chan int)
上述代码创建一个容量为 0 的 Channel,类型是 int,容量为 0 表示这个 Channel 不能缓存任何数据。这种 Channel 模式被成为 同步模式。
1.1 Channel 模式
Golang 中的 Channel 拥有两种模式:同步和异步
同步模式
同步模式是指在往 Channel 中发送或接收值时,程序将会被阻塞(block),直到发送和接收完成。
容量为 0 的 Channel 是同步模式,可以使用 len() 函数获取 Channel 中元素的数量,使用 cap() 函数获取 Channel 的容量。以下是一个示例:
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
ch <- 3
}()
fmt.Println("Channel length:", len(ch))
fmt.Println("Channel capacity:", cap(ch))
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
上述代码创建了一个容量为 0 的 Channel,然后启动了一个新的 Goroutines,发送了三个整数到这个 Channel 中。
在主程序中,使用 len() 函数获取到了 Channel 的长度,输出 0。这是因为容量为 0 的 Channel 是同步模式,只有在有接收者可以接收到值时才会将值发送到 Channel 中。
第一次执行 <-ch 时,程序被阻塞,直到 Channel 中有值可以接收,这时才会输出 1。后面的两个接收动作也会被阻塞,直到有值可以接收为止,依次输出 2 和 3。
异步模式
异步模式是指,在往 Channel 中发送或接收值时,程序不会被阻塞,如果 Channel 已满或者为空,则发送或接收操作会立即返回。异步模式需要在创建 Channel 时指定 Channel 的容量。
以下是异步模式创建 Channel 的语法:
ch := make(chan int, 5)
上述代码创建了一个容量为 5 的 Channel,可以缓存五个整数。以下是一个异步模式的示例:
ch := make(chan int, 1)
ch <- 1
go func() {
ch <- 2
}()
fmt.Println(<-ch)
fmt.Println(<-ch)
上述代码在创建 Channel 时指定了容量为 1,然后向 Channel 中发送了整数 1,此时 Channel 已经有一个值了,接着启动在新的 Goroutines 中,向 Channel 中发送整数 2,由于 Channel 容量为 1,所以 2 没有被阻塞,可以直接发送。
在主程序中,先执行了 <-ch,程序被阻塞,直到有值可以接收,此时输出 1;紧接着执行了第二次的 <-ch,此时程序不会被阻塞,因为 Channel 中已经有值可以接收,直接输出 2。
2. Channel 的容量和长度
在上面的代码示例中,我们已经接触到了 Channel 的容量和长度。在 Golang 中,Channel 的容量和长度可以使用 built-in 函数 len() 和 cap() 来获取。
2.1 Channel 的容量
Channel 的容量指的是它所能缓存的元素的数量。对于同步模式的 Channel,它的容量是 0,如果试图向 Channel 中发送某个值,但是 Channel 中没有接收程序,那么发送操作将会被阻塞。
对于异步模式的 Channel,它的容量只受内存限制,我们可以在创建 Channel 时指定其容量大小。在调用 make() 函数时,通常会同时指定 Channel 的类型和容量,语法如下:
ch := make(chan int, 10)
上述代码创建了一个 int 类型的 Channel,其容量为 10,可以缓存 10 个 int 类型的数据。
2.2 Channel 的长度
Channel 的长度指的是它当前缓存的元素数量。对于同步模式的 Channel,它的长度总是 0。对于异步模式的 Channel,其长度可以通过 len() 函数来获取。
以下是一个异步模式 Channel 长度的示例:
ch := make(chan int, 5)
ch <- 1
ch <- 2
fmt.Println("Channel length:", len(ch))
上述代码向一个容量为 5 的 Channel 中发送两个整数,然后通过 len() 函数获取其长度。由于发送了两个元素,所以 Channel 的长度为 2。
需要注意的是,向一个已满的 Channel 中发送元素将导致程序被阻塞。以下是一个示例:
ch := make(chan int, 2)
ch <- 1
ch <- 2
// 下面这行代码会被阻塞
ch <- 3
上述代码创建了一个容量为 2 的 Channel,并向其中发送了两个元素。由于 Channel 已经满了,继续向其中发送元素将会阻塞程序。
3. Channel 的应用
在 Golang 语言中,Channel 的出现,使得实现并发编程变得更加简单和直观。Channel 提供了一种非常灵活的方式,使得不同 Goroutines 的并发任务可以在通信和同步的情况下完成。
3.1 并发模型
与多线程编程不同,在 Golang 中通过并发编程可以更好地发挥 CPU 的性能,同时也避免了因为共享数据而带来的一些负面影响。Channel 的出现,为 Goroutines 之间的通信和同步提供了便捷的方式,通过 Channel,我们可以在不同的 Goroutines 之间交换数据,以此来协调他们的行为。
以下是一个使用 Channel 实现并发计数器的示例:
func counter(channel chan int) {
for i := 1; i <= 5; i++ {
channel <- i
}
close(channel)
}
func printer(channel chan int) {
for num := range channel {
fmt.Println(num)
time.Sleep(time.Second)
}
}
func main() {
channel := make(chan int)
go counter(channel)
printer(channel)
}
上述代码中,我们创建了两个函数 counter() 和 printer(),并在主函数中启动了两个新的 Goroutines 来执行这两个函数。
counter() 函数通过一个带有 int 类型参数的 Channel,对数字 1 到 5 进行计数,并将结果发送到 Channel 中。但是在发送全部计数结果后,需要调用内置函数 close() 关闭 Channel。
printer() 函数从同一个 Channel 中接收数据,并将他们输出到终端,同时在每个数字的输出之间休眠 1 秒钟,以确保输出顺序。
在主函数中,我们创建了一个名为 channel 的 Channel,并将其传递给 counter() 函数,随后启动 Goroutine 并在其中执行 printer() 函数。
运行上述代码,输出如下:
1
2
3
4
5
从上述示例中可以看出,在 Golang 中使用 Channel 来协调 Goroutines 的行为可以带来非常好的效果。通过 Channel,我们可以实现 Golang 中的经典同步模式——生产者消费者模式。
3.2 生产者消费者模式
生产者消费者模式是一种经典的并发编程模式,其中生产者和消费者都运行在不同的 Goroutines 中,通过使用一个共享的 Channel 来协调它们的行为。生产者进程从外部输入获取一个元素并将其放入到 Channel 中,而消费者进程则从 Channel 中获取这个元素并对其进行处理。
以下是一个实现生产者消费者模式的示例:
func produce(channel chan int) {
for i := 1; i <= 10; i++ {
channel <- i
}
close(channel)
}
func consume(channel chan int) {
for num := range channel {
fmt.Println(num)
}
}
func main() {
channel := make(chan int)
go produce(channel)
consume(channel)
}
在上述代码中,我们定义了两个函数 produce() 和 consume(),用来分别模拟生产者和消费者的行为。
produce() 函数通过一个带有 int 参数的 Channel,将数字 1 到 10 存储到其中,并在全部输入完成后关闭 Channel。
consume() 函数从同一 Channel 中读取数据,并将输出到终端上。
在主函数中,我们创建了一个名为 channel 的 Channel,并将其作为参数传递给 produce() 函数,在新的 Goroutine 中执行。使用 consume() 函数在主函数中消费 Channel 中的元素。
运行上述代码,输出如下:
1
2
3
4
5
6
7
8
9
10
从上述代码中我们可以看到如何使用 Channel 实现了生产者消费者模式,这种模式可以很方便地协调和控制程序的行为,避免了多线程带来的一些困扰。
总结
在 Golang 中,Channels 是一种非常重要的概念,它允许不同的 Goroutines 进行通信和协作。Channels 是一种基于消息传递的并发模型,使用起来非常直观和简单,可以大大提高程序的并发性能。
Channels 有两种模式:同步和异步,同步模式会阻塞程序,异步模式不会。除此之外,我们还可以通过查看 Channel 的长度和容量来了解其内部状态。
在 Golang 中使用 Channel 的方式非常灵活,可以通过 Channel 来实现各种不同的并发编程模型,比如并发计数器和生产者消费者模式。