面试题答案
一键面试1. goroutine调度策略优化
- 合理设置GOMAXPROCS:
runtime.GOMAXPROCS
函数用于设置可以并行计算的CPU核数。根据服务器CPU核心数合理设置,例如:
package main
import (
"fmt"
"runtime"
)
func main() {
numCPU := runtime.NumCPU()
runtime.GOMAXPROCS(numCPU)
fmt.Printf("设置GOMAXPROCS为: %d\n", numCPU)
}
这样可以让Go运行时系统充分利用多核CPU的性能,提高并发执行效率。
- 限制goroutine数量:使用
sync.WaitGroup
和channel
来限制同时运行的goroutine数量。假设我们有一个任务列表,需要限制同时执行的任务数为10:
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup, taskChan <-chan int) {
defer wg.Done()
for task := range taskChan {
fmt.Printf("Worker %d processing task %d\n", id, task)
// 模拟复杂计算和I/O操作
}
}
func main() {
var wg sync.WaitGroup
taskChan := make(chan int, 100)
maxWorkers := 10
for i := 0; i < maxWorkers; i++ {
wg.Add(1)
go worker(i, &wg, taskChan)
}
for i := 0; i < 100; i++ {
taskChan <- i
}
close(taskChan)
wg.Wait()
}
2. channel缓冲设置
- 合理设置缓冲大小:对于需要大量数据传输的channel,设置合适的缓冲大小可以减少阻塞。例如,在生产者 - 消费者模型中,如果生产者生产数据速度较快,消费者处理速度相对较慢,可以适当增大channel的缓冲。
package main
import (
"fmt"
"sync"
)
func producer(wg *sync.WaitGroup, ch chan<- int) {
defer wg.Done()
for i := 0; i < 100; i++ {
ch <- i
}
close(ch)
}
func consumer(wg *sync.WaitGroup, ch <-chan int) {
defer wg.Done()
for num := range ch {
fmt.Printf("Consumed: %d\n", num)
}
}
func main() {
var wg sync.WaitGroup
ch := make(chan int, 20) // 设置缓冲大小为20
wg.Add(2)
go producer(&wg, ch)
go consumer(&wg, ch)
wg.Wait()
}
- 无缓冲channel用于同步:在需要精确同步的场景,使用无缓冲channel。例如,多个goroutine需要按顺序执行某些操作:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
ch1 := make(chan struct{})
ch2 := make(chan struct{})
wg.Add(2)
go func() {
defer wg.Done()
fmt.Println("Goroutine 1 start")
<-ch1
fmt.Println("Goroutine 1 end")
}()
go func() {
defer wg.Done()
fmt.Println("Goroutine 2 start")
ch1 <- struct{}{}
<-ch2
fmt.Println("Goroutine 2 end")
}()
ch2 <- struct{}{}
wg.Wait()
}
3. 内存管理
- 及时释放资源:在使用完资源后,及时关闭文件、数据库连接等。例如,在进行文件I/O操作时:
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("test.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
// 文件操作
}
- 减少内存分配:对于频繁创建和销毁的对象,可以使用对象池(
sync.Pool
)。例如,在高并发场景下需要频繁创建bytes.Buffer
对象:
package main
import (
"bytes"
"fmt"
"sync"
)
var bufferPool = &sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
func main() {
buf := bufferPool.Get().(*bytes.Buffer)
buf.WriteString("Hello, World!")
fmt.Println(buf.String())
buf.Reset()
bufferPool.Put(buf)
}
4. 使用sync包工具
- sync.Mutex用于互斥访问:当多个goroutine需要访问共享资源时,使用
sync.Mutex
来保证同一时间只有一个goroutine可以访问。例如,多个goroutine对一个计数器进行操作:
package main
import (
"fmt"
"sync"
)
type Counter struct {
value int
mu sync.Mutex
}
func (c *Counter) Increment() {
c.mu.Lock()
c.value++
c.mu.Unlock()
}
func (c *Counter) Get() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
func main() {
var wg sync.WaitGroup
counter := Counter{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("Counter value:", counter.Get())
}
- sync.Cond用于条件等待:当某个条件满足时才执行某些操作。例如,在生产者 - 消费者模型中,当缓冲区满时生产者等待,当缓冲区有数据时消费者才消费:
package main
import (
"fmt"
"sync"
)
type Buffer struct {
data []int
maxLen int
mu sync.Mutex
cond *sync.Cond
}
func NewBuffer(maxLen int) *Buffer {
b := &Buffer{
data: make([]int, 0, maxLen),
maxLen: maxLen,
}
b.cond = sync.NewCond(&b.mu)
return b
}
func (b *Buffer) Produce(item int) {
b.mu.Lock()
for len(b.data) == b.maxLen {
b.cond.Wait()
}
b.data = append(b.data, item)
b.cond.Broadcast()
b.mu.Unlock()
}
func (b *Buffer) Consume() int {
b.mu.Lock()
for len(b.data) == 0 {
b.cond.Wait()
}
item := b.data[0]
b.data = b.data[1:]
b.cond.Broadcast()
b.mu.Unlock()
return item
}
func main() {
buffer := NewBuffer(5)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
buffer.Produce(i)
fmt.Printf("Produced: %d\n", i)
}
}()
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
item := buffer.Consume()
fmt.Printf("Consumed: %d\n", item)
}
}()
wg.Wait()
}
5. 死锁分析及解决方案
- 死锁原因:死锁通常发生在多个goroutine相互等待对方释放资源的情况下。例如,两个goroutine分别持有不同的锁,并且都试图获取对方的锁:
package main
import (
"fmt"
"sync"
)
var mu1 sync.Mutex
var mu2 sync.Mutex
func goroutine1() {
mu1.Lock()
fmt.Println("Goroutine 1 locked mu1")
mu2.Lock()
fmt.Println("Goroutine 1 locked mu2")
mu2.Unlock()
mu1.Unlock()
}
func goroutine2() {
mu2.Lock()
fmt.Println("Goroutine 2 locked mu2")
mu1.Lock()
fmt.Println("Goroutine 2 locked mu1")
mu1.Unlock()
mu2.Unlock()
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
goroutine1()
}()
go func() {
defer wg.Done()
goroutine2()
}()
wg.Wait()
}
- 解决方案:按照固定顺序获取锁,避免交叉获取。修改上述代码如下:
package main
import (
"fmt"
"sync"
)
var mu1 sync.Mutex
var mu2 sync.Mutex
func goroutine1() {
mu1.Lock()
fmt.Println("Goroutine 1 locked mu1")
mu2.Lock()
fmt.Println("Goroutine 1 locked mu2")
mu2.Unlock()
mu1.Unlock()
}
func goroutine2() {
mu1.Lock()
fmt.Println("Goroutine 2 locked mu1")
mu2.Lock()
fmt.Println("Goroutine 2 locked mu2")
mu2.Unlock()
mu1.Unlock()
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
goroutine1()
}()
go func() {
defer wg.Done()
goroutine2()
}()
wg.Wait()
}
6. 资源泄漏分析及解决方案
- 资源泄漏原因:在高并发场景下,如果没有及时关闭文件、数据库连接等资源,或者goroutine发生panic但没有正确处理,可能导致资源泄漏。例如,在一个处理HTTP请求的goroutine中打开了数据库连接,但没有关闭:
package main
import (
"database/sql"
"fmt"
_ "github.com/lib/pq" // 假设使用PostgreSQL
)
func handleRequest() {
db, err := sql.Open("postgres", "user=postgres dbname=mydb sslmode=disable")
if err != nil {
fmt.Println("Error opening database:", err)
return
}
// 处理请求,没有关闭数据库连接
}
func main() {
for i := 0; i < 100; i++ {
go handleRequest()
}
// 等待一段时间
}
- 解决方案:使用
defer
语句确保资源在函数结束时关闭,并且使用recover
来处理goroutine中的panic。修改上述代码如下:
package main
import (
"database/sql"
"fmt"
_ "github.com/lib/pq" // 假设使用PostgreSQL
)
func handleRequest() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
db, err := sql.Open("postgres", "user=postgres dbname=mydb sslmode=disable")
if err != nil {
fmt.Println("Error opening database:", err)
return
}
defer db.Close()
// 处理请求
}
func main() {
for i := 0; i < 100; i++ {
go handleRequest()
}
// 等待一段时间
}