面试题答案
一键面试理论层面
- 资源池化:
- 对于有限资源如文件句柄、数据库连接等,使用资源池。资源池可以预先创建一定数量的资源,Goroutine需要时从池中获取,使用完毕后归还。这样避免了频繁创建和销毁资源的开销,同时能控制资源使用总量,防止资源耗尽。
- 例如在数据库连接场景下,使用连接池可以减少数据库连接的创建和关闭次数,提高数据库操作效率。
- 控制Goroutine数量:
- 使用
sync.WaitGroup
结合有缓冲的通道来限制同时运行的Goroutine数量。有缓冲的通道相当于一个信号量,通道的缓冲区大小决定了允许同时运行的Goroutine数量上限。 - 例如如果系统最多能承受100个Goroutine同时运行处理任务,就创建一个缓冲区大小为100的通道。
- 使用
- 避免内存泄漏:
- 确保Goroutine中使用的资源(如文件句柄)在Goroutine结束时正确关闭。使用
defer
关键字可以方便地实现这一点,无论Goroutine以何种方式结束(正常结束或发生错误),defer
后的语句都会执行,进行资源清理。 - 对于长时间运行的Goroutine,要注意避免在其中不断累积数据导致内存占用不断上升。定期清理不再使用的数据结构。
- 确保Goroutine中使用的资源(如文件句柄)在Goroutine结束时正确关闭。使用
- 处理Goroutine泄漏:
- 定期检查是否有未结束的Goroutine。可以在程序中设置一个监控机制,例如使用
time.Ticker
定时检查某些全局变量(如记录Goroutine状态的变量),判断是否有Goroutine长时间未完成任务且无预期行为,若有则进行相应处理(如记录日志、尝试结束该Goroutine)。 - 确保在Goroutine结束时,所有依赖的资源和通道都已正确关闭或处理,避免因为通道未关闭等原因导致Goroutine阻塞无法结束。
- 定期检查是否有未结束的Goroutine。可以在程序中设置一个监控机制,例如使用
代码实现层面
- 资源池实现示例(以文件句柄资源池为例):
package main
import (
"fmt"
"io/ioutil"
"sync"
)
type FileHandle struct {
// 文件句柄相关结构
}
type FilePool struct {
pool sync.Pool
}
func NewFilePool() *FilePool {
return &FilePool{
pool: sync.Pool{
New: func() interface{} {
// 创建新的文件句柄
file, err := ioutil.TempFile("", "example")
if err != nil {
panic(err)
}
return &FileHandle{/* 初始化文件句柄相关结构 */}
},
},
}
}
func (fp *FilePool) Get() *FileHandle {
return fp.pool.Get().(*FileHandle)
}
func (fp *FilePool) Put(fh *FileHandle) {
// 清理文件句柄相关资源
// 例如关闭文件等操作
fp.pool.Put(fh)
}
- 控制Goroutine数量示例:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
semaphore := make(chan struct{}, 100) // 最多允许100个Goroutine同时运行
for i := 0; i < 1000; i++ {
wg.Add(1)
semaphore <- struct{}{} // 获取信号量
go func(id int) {
defer func() {
<-semaphore // 释放信号量
wg.Done()
}()
// 具体任务处理逻辑
fmt.Printf("Goroutine %d is running\n", id)
}(i)
}
wg.Wait()
}
- 避免内存泄漏和处理Goroutine泄漏示例:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup, stopCh chan struct{}) {
defer wg.Done()
select {
case <-stopCh:
// 收到停止信号,进行清理操作
fmt.Printf("Goroutine %d stopped\n", id)
return
case <-time.After(2 * time.Second):
// 模拟任务执行2秒
fmt.Printf("Goroutine %d finished task\n", id)
}
}
func main() {
var wg sync.WaitGroup
stopCh := make(chan struct{})
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(i, &wg, stopCh)
}
// 模拟10秒后检查并停止所有Goroutine
time.Sleep(10 * time.Second)
close(stopCh)
wg.Wait()
fmt.Println("All Goroutines stopped")
}
在上述代码中,worker
函数中使用 defer wg.Done()
确保Goroutine结束时通知 WaitGroup
。通过 stopCh
通道可以向Goroutine发送停止信号,避免Goroutine泄漏。同时在 worker
函数中可以进行资源清理操作(如关闭文件句柄等),防止内存泄漏。