面试题答案
一键面试区别
- 非缓冲Channel:
- 也叫同步Channel,发送和接收操作会阻塞,直到对应的接收和发送操作准备好。也就是说,当一个协程向非缓冲Channel发送数据时,它会一直阻塞,直到另一个协程从该Channel接收数据;反之,当一个协程尝试从非缓冲Channel接收数据时,它会阻塞,直到有其他协程向该Channel发送数据。
- 非缓冲Channel保证数据的同步传递,数据从发送端直接传递到接收端,没有中间的缓存空间。
- 缓冲Channel:
- 具有一定的缓存容量,可以在没有接收者的情况下,先缓冲一定数量的数据。只有当Channel的缓存被填满时,再进行发送操作才会阻塞;同样,只有当Channel为空时,接收操作才会阻塞。
- 缓冲Channel允许在一定程度上解耦发送者和接收者的操作,提高了并发操作的灵活性。
场景举例
- 缓冲Channel适用场景:
- 生产者 - 消费者模型: 例如,有一个生产者协程不断生成数据,消费者协程处理数据。如果生产者生成数据的速度比消费者处理数据的速度快一些,并且允许一定程度的堆积,可以使用缓冲Channel。
package main
import (
"fmt"
)
func producer(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
fmt.Printf("Produced: %d\n", i)
}
close(ch)
}
func consumer(ch chan int) {
for num := range ch {
fmt.Printf("Consumed: %d\n", num)
}
}
func main() {
ch := make(chan int, 5)
go producer(ch)
go consumer(ch)
select {}
}
在这个例子中,ch
是一个缓冲容量为5的Channel。生产者可以先向Channel中发送最多5个数据,而不会阻塞,消费者在合适的时候从Channel中取出数据。这在一定程度上缓解了生产者和消费者速度不匹配的问题。
2. 非缓冲Channel适用场景:
- 同步操作: 假设需要两个协程严格按照顺序执行某些操作,例如一个协程进行初始化操作,另一个协程在初始化完成后再开始执行任务。
package main
import (
"fmt"
)
func initializer(ch chan struct{}) {
fmt.Println("Initializing...")
// 模拟一些初始化工作
ch <- struct{}{}
fmt.Println("Initialization completed")
}
func worker(ch chan struct{}) {
<-ch
fmt.Println("Starting work...")
}
func main() {
ch := make(chan struct{})
go initializer(ch)
go worker(ch)
select {}
}
这里使用非缓冲Channel ch
来确保worker
协程在initializer
协程完成初始化(通过向ch
发送数据)后才开始工作,实现了严格的同步。