MST

星途 面试题库

面试题:Go并发编程中channel的使用陷阱

在Go的并发编程中,channel是重要的通信机制。请列举至少两个在使用channel时可能遇到的陷阱,并说明如何避免这些陷阱,例如在无缓冲和有缓冲channel的场景下,死锁是如何产生的又该如何避免。
23.0万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 死锁陷阱
    • 无缓冲channel死锁
      • 产生原因:在无缓冲channel中,发送操作会阻塞直到有接收者准备好接收数据,接收操作会阻塞直到有数据发送过来。如果在一个goroutine中只进行发送操作,而在另一个goroutine中没有及时进行接收操作,就会导致死锁。例如:
package main

func main() {
    ch := make(chan int)
    ch <- 1 // 这里会阻塞,因为没有接收者
}
  - **避免方法**:确保在发送数据前有对应的接收者准备好接收。可以启动一个goroutine来接收数据,例如:
package main

import "fmt"

func main() {
    ch := make(chan int)
    go func() {
        fmt.Println(<-ch)
    }()
    ch <- 1
}
- **有缓冲channel死锁**:
  - **产生原因**:当有缓冲channel已满,继续向其发送数据时会阻塞;当channel已空,继续从其接收数据时会阻塞。如果在这种阻塞情况下,没有其他goroutine来解除阻塞,就会导致死锁。例如:
package main

func main() {
    ch := make(chan int, 1)
    ch <- 1
    ch <- 2 // 这里会阻塞,因为channel缓冲区已满
}
  - **避免方法**:合理规划channel的缓冲区大小,并且确保在合适的时候进行接收操作以释放缓冲区空间。可以通过在发送一定数量数据后,启动goroutine进行接收,例如:
package main

import "fmt"

func main() {
    ch := make(chan int, 1)
    go func() {
        for i := 0; i < 2; i++ {
            fmt.Println(<-ch)
        }
    }()
    ch <- 1
    ch <- 2
}
  1. 未关闭channel导致泄漏
    • 产生原因:如果一个goroutine从channel接收数据,但该channel永远不会关闭,这个goroutine将永远阻塞,造成资源泄漏。例如:
package main

func readChan(ch chan int) {
    for {
        v, ok := <-ch
        if!ok {
            return
        }
        // 处理数据v
    }
}

如果调用readChan时传入的ch没有关闭,for循环会一直阻塞。 - 避免方法:在数据发送完毕后,及时关闭channel。例如:

package main

import "fmt"

func readChan(ch chan int) {
    for v := range ch {
        fmt.Println(v)
    }
}

func main() {
    ch := make(chan int)
    go func() {
        for i := 0; i < 5; i++ {
            ch <- i
        }
        close(ch)
    }()
    readChan(ch)
}
  1. 向已关闭channel发送数据
    • 产生原因:向已关闭的channel发送数据会导致运行时恐慌(panic)。例如:
package main

func main() {
    ch := make(chan int)
    close(ch)
    ch <- 1 // 这里会导致panic
}
- **避免方法**:在发送数据前,先判断channel是否关闭。可以通过使用`select`语句结合`default`分支来避免向已关闭的channel发送数据,例如:
package main

import "fmt"

func main() {
    ch := make(chan int)
    close(ch)
    select {
    case ch <- 1:
    default:
        fmt.Println("channel已关闭,不能发送数据")
    }
}