面试题答案
一键面试生产者 - 消费者模型实现
- 定义数据结构和通道:
首先定义一个通道用于生产者和消费者之间传递数据。
package main import ( "fmt" ) func main() { // 创建一个有缓冲的通道,用于传递数据 dataChan := make(chan int, 10) // 这里10表示通道的缓冲区大小,可以根据实际需求调整 }
- 生产者函数:
生产者函数不断生成数据并发送到通道中。
在这个函数中,通过func producer(dataChan chan<- int) { for i := 0; i < 10; i++ { dataChan <- i fmt.Printf("Produced: %d\n", i) } close(dataChan) }
<-
操作符表示这个通道只用于发送数据。当数据生产完毕后,通过close
函数关闭通道,这样消费者可以知道数据已经生产完毕。 - 消费者函数:
消费者函数从通道中接收数据并处理。
这里通过func consumer(dataChan <-chan int) { for data := range dataChan { fmt.Printf("Consumed: %d\n", data) } }
<-
操作符表示这个通道只用于接收数据。for... range
循环会不断从通道中接收数据,直到通道关闭。 - 主函数中启动goroutine:
在主函数中启动生产者和消费者的goroutine。
通过func main() { dataChan := make(chan int, 10) go producer(dataChan) go consumer(dataChan) // 防止主函数退出 select {} }
go
关键字启动goroutine,使生产者和消费者并发执行。最后的select {}
语句会阻塞主函数,防止程序退出。
避免死锁
- 确保通道操作匹配:
- 避免只发送不接收或只接收不发送的情况。例如,如果一个通道只用于发送,那么在代码中就不要有接收操作,反之亦然。在上述例子中,
producer
函数只向dataChan
发送数据,consumer
函数只从dataChan
接收数据。
- 避免只发送不接收或只接收不发送的情况。例如,如果一个通道只用于发送,那么在代码中就不要有接收操作,反之亦然。在上述例子中,
- 及时关闭通道:
- 生产者完成数据生产后要及时关闭通道,如
producer
函数中的close(dataChan)
。这样消费者的for... range
循环可以正常结束,不会因为一直等待接收数据而死锁。
- 生产者完成数据生产后要及时关闭通道,如
- 使用有缓冲通道:
- 合理设置通道的缓冲区大小,避免缓冲区满导致发送操作阻塞,或缓冲区空导致接收操作阻塞。例如,
make(chan int, 10)
创建了一个有10个缓冲的通道,在一定程度上可以减少阻塞情况的发生,但也要根据实际数据量和处理速度来调整缓冲区大小。
- 合理设置通道的缓冲区大小,避免缓冲区满导致发送操作阻塞,或缓冲区空导致接收操作阻塞。例如,
- 避免循环依赖:
- 如果有多个goroutine和通道相互依赖,要确保没有循环依赖关系。例如,goroutine A等待goroutine B发送数据,goroutine B又等待goroutine A发送数据,这种情况就会导致死锁。在设计并发逻辑时,要仔细梳理各个goroutine之间的依赖关系,避免出现这种循环。