MST

星途 面试题库

面试题:Go语言构建服务器端应用之并发控制

在使用Go构建高性能服务器端应用时,常常会涉及到并发操作。请描述一下如何使用Go的goroutine和channel来实现一个简单的生产者 - 消费者模型,并且说明如何避免死锁。
13.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

生产者 - 消费者模型实现

  1. 定义数据结构和通道: 首先定义一个通道用于生产者和消费者之间传递数据。
    package main
    
    import (
        "fmt"
    )
    
    func main() {
        // 创建一个有缓冲的通道,用于传递数据
        dataChan := make(chan int, 10)
        // 这里10表示通道的缓冲区大小,可以根据实际需求调整
    }
    
  2. 生产者函数: 生产者函数不断生成数据并发送到通道中。
    func producer(dataChan chan<- int) {
        for i := 0; i < 10; i++ {
            dataChan <- i
            fmt.Printf("Produced: %d\n", i)
        }
        close(dataChan)
    }
    
    在这个函数中,通过<-操作符表示这个通道只用于发送数据。当数据生产完毕后,通过close函数关闭通道,这样消费者可以知道数据已经生产完毕。
  3. 消费者函数: 消费者函数从通道中接收数据并处理。
    func consumer(dataChan <-chan int) {
        for data := range dataChan {
            fmt.Printf("Consumed: %d\n", data)
        }
    }
    
    这里通过<-操作符表示这个通道只用于接收数据。for... range循环会不断从通道中接收数据,直到通道关闭。
  4. 主函数中启动goroutine: 在主函数中启动生产者和消费者的goroutine。
    func main() {
        dataChan := make(chan int, 10)
    
        go producer(dataChan)
        go consumer(dataChan)
    
        // 防止主函数退出
        select {}
    }
    
    通过go关键字启动goroutine,使生产者和消费者并发执行。最后的select {}语句会阻塞主函数,防止程序退出。

避免死锁

  1. 确保通道操作匹配
    • 避免只发送不接收或只接收不发送的情况。例如,如果一个通道只用于发送,那么在代码中就不要有接收操作,反之亦然。在上述例子中,producer函数只向dataChan发送数据,consumer函数只从dataChan接收数据。
  2. 及时关闭通道
    • 生产者完成数据生产后要及时关闭通道,如producer函数中的close(dataChan)。这样消费者的for... range循环可以正常结束,不会因为一直等待接收数据而死锁。
  3. 使用有缓冲通道
    • 合理设置通道的缓冲区大小,避免缓冲区满导致发送操作阻塞,或缓冲区空导致接收操作阻塞。例如,make(chan int, 10)创建了一个有10个缓冲的通道,在一定程度上可以减少阻塞情况的发生,但也要根据实际数据量和处理速度来调整缓冲区大小。
  4. 避免循环依赖
    • 如果有多个goroutine和通道相互依赖,要确保没有循环依赖关系。例如,goroutine A等待goroutine B发送数据,goroutine B又等待goroutine A发送数据,这种情况就会导致死锁。在设计并发逻辑时,要仔细梳理各个goroutine之间的依赖关系,避免出现这种循环。