面试题答案
一键面试阻塞操作适用场景及示例
- 同步协程
- 场景描述:当需要确保一个协程完成某些任务后,另一个协程才能继续执行时,阻塞操作非常有用。例如,在一个程序中,有一个协程负责数据的计算,另一个协程负责使用计算结果进行输出,通过阻塞通道来同步这两个协程。
- 示例代码:
package main
import (
"fmt"
)
func calculate(ch chan int) {
result := 1 + 2
ch <- result
}
func main() {
ch := make(chan int)
go calculate(ch)
result := <-ch
fmt.Println("计算结果:", result)
}
在这个例子中,main
函数中的 <-ch
操作是阻塞的,直到 calculate
协程向通道 ch
发送数据,这样就保证了 calculate
协程的计算任务完成后,main
函数才会继续输出结果。
2. 资源池管理
- 场景描述:假设有一个数据库连接池,每次从连接池中获取连接和归还连接时,都希望在没有可用连接时等待,直到有连接可用。通道的阻塞操作可以很好地实现这种资源管理。
- 示例代码:
package main
import (
"fmt"
)
func main() {
pool := make(chan struct{}, 2) // 连接池,最多容纳2个连接
pool <- struct{}{} // 初始化放入一个连接
pool <- struct{}{} // 再放入一个连接
// 获取连接
fmt.Println("尝试获取连接")
<-pool
fmt.Println("获取到一个连接")
// 再次获取连接
fmt.Println("尝试获取第二个连接")
<-pool
fmt.Println("获取到第二个连接")
// 尝试获取第三个连接,此时会阻塞
fmt.Println("尝试获取第三个连接")
// 这里程序会阻塞,因为没有可用连接
}
在这个代码中,通道 pool
模拟连接池,每次从通道接收数据(<-pool
)就相当于获取一个连接,当没有可用连接时,接收操作会阻塞,直到有连接归还(向通道发送数据)。
非阻塞操作适用场景及示例
- 防止死锁
- 场景描述:在复杂的并发程序中,可能存在多个协程互相等待资源的情况,容易导致死锁。使用非阻塞通道操作可以在某些情况下避免死锁。例如,在一个网络通信程序中,发送数据到网络可能会因为网络故障等原因阻塞,如果使用阻塞通道发送数据,可能会导致死锁。通过非阻塞发送操作,可以在发送失败时采取其他措施,如重试或记录日志。
- 示例代码:
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
select {
case ch <- 1:
fmt.Println("数据发送成功")
default:
fmt.Println("数据发送失败,采取其他措施,如重试或记录日志")
}
}
在这个 select
语句中,ch <- 1
是一个向通道 ch
发送数据的操作,如果通道 ch
已满(这里没有接收方,所以可视为已满),则会执行 default
分支,避免了阻塞,也就避免了可能因阻塞发送而导致的死锁。
2. 异步任务处理
- 场景描述:在处理大量异步任务时,不希望因为向通道发送任务而阻塞当前协程,影响整体处理效率。例如,一个 Web 服务器需要处理大量的请求,每个请求可能触发一个异步任务,使用非阻塞通道可以快速将任务放入通道,而不会阻塞处理请求的协程。
- 示例代码:
package main
import (
"fmt"
)
func worker(ch chan int) {
for task := range ch {
fmt.Println("处理任务:", task)
}
}
func main() {
ch := make(chan int)
go worker(ch)
tasks := []int{1, 2, 3, 4, 5}
for _, task := range tasks {
select {
case ch <- task:
fmt.Println("任务", task, "已提交")
default:
fmt.Println("任务", task, "提交失败,队列已满,可稍后重试")
}
}
close(ch)
}
在这个示例中,main
函数通过 select
语句以非阻塞方式向 ch
通道发送任务。如果通道已满,任务提交失败,程序可以选择采取其他处理方式,如记录日志或稍后重试。而 worker
协程从通道接收任务并处理,这样实现了异步任务处理。