面试题答案
一键面试可能遇到的问题
- 死锁:
- 原因:在使用
WaitGroup
时,如果Add
操作在Wait
操作之后执行,且Add
的数量大于0,Wait
会一直阻塞,导致死锁。例如,在一个协程中调用wg.Wait()
,而wg.Add(1)
却在另一个协程中,并且这个协程还未启动或者启动后还未执行到wg.Add(1)
,就会出现死锁。 - 另一种情况:如果在
Wait
之后又意外调用了Add
操作,也可能导致死锁,因为Wait
已经返回,不会再等待后续添加的任务。
- 原因:在使用
- 资源浪费:
- 原因:如果在高并发场景下,大量的下载任务被添加到
WaitGroup
,但每个任务下载完成后没有及时释放相关资源(如网络连接、内存缓存等),就会造成资源浪费。例如,每个下载任务都占用一个网络连接,任务完成后没有关闭连接,随着任务数量的增加,会占用大量的网络连接资源。
- 原因:如果在高并发场景下,大量的下载任务被添加到
- 数据竞争:
- 原因:在更新
WaitGroup
的计数器(Add
、Done
操作)时,如果多个协程同时进行操作,可能会导致数据竞争问题。虽然WaitGroup
本身是线程安全的,但如果在其使用过程中与其他共享变量交互不当,就可能引发数据竞争。例如,一个协程在Done
操作后需要更新一个共享的任务完成计数变量,如果多个协程同时进行这个操作,就可能导致数据不一致。
- 原因:在更新
避免问题的方法
- 避免死锁:
- 确保顺序:始终在
Wait
之前调用Add
,并且保证Add
的数量与Done
的数量一致。可以在主协程中初始化WaitGroup
并设置正确的任务数量,然后再启动各个下载任务协程。 - 检查逻辑:仔细检查代码逻辑,避免在
Wait
之后意外调用Add
。可以通过代码审查和测试来确保这种情况不会发生。
- 确保顺序:始终在
- 避免资源浪费:
- 及时释放资源:在每个下载任务完成时(在
Done
之前或之后),确保及时关闭网络连接、释放内存缓存等相关资源。例如,使用defer
语句在函数结束时关闭文件句柄、网络连接等资源。 - 资源复用:考虑使用资源池来复用网络连接等资源,减少资源创建和销毁的开销。例如,使用连接池来管理数据库连接或网络连接。
- 及时释放资源:在每个下载任务完成时(在
- 避免数据竞争:
- 使用互斥锁:如果在
WaitGroup
操作过程中需要与其他共享变量交互,可以使用互斥锁(sync.Mutex
)来保护共享变量。例如,在更新共享的任务完成计数变量时,先获取互斥锁,更新完成后再释放互斥锁。 - 通道通信:可以使用通道(
chan
)来安全地传递数据,避免共享变量带来的数据竞争问题。例如,通过通道传递任务完成的信号,而不是直接操作共享变量。
- 使用互斥锁:如果在
针对死锁问题的解决方案示例代码
package main
import (
"fmt"
"sync"
)
func downloadFileFragment(url string, wg *sync.WaitGroup) {
defer wg.Done()
// 模拟文件片段下载
fmt.Printf("Downloading from %s\n", url)
}
func main() {
var wg sync.WaitGroup
urls := []string{
"http://node1/file1",
"http://node2/file2",
"http://node3/file3",
}
// 正确设置任务数量
wg.Add(len(urls))
for _, url := range urls {
go downloadFileFragment(url, &wg)
}
// 等待所有任务完成
wg.Wait()
fmt.Println("All file fragments downloaded.")
}
在上述代码中,先调用wg.Add(len(urls))
设置任务数量,然后启动各个下载任务协程,最后调用wg.Wait()
等待所有任务完成,避免了死锁问题。