面试题答案
一键面试调试手段
- 使用
go build -race
和go test -race
: 在构建或测试代码时带上-race
标志,Go的竞态检测器会检测到竞态条件并输出详细信息。例如,假设有如下代码:
package main
import (
"fmt"
"sync"
)
var num int
func increment(wg *sync.WaitGroup) {
num++
wg.Done()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final value:", num)
}
运行go run -race main.go
,会输出类似如下信息(具体信息因实际情况而异):
==================
WARNING: DATA RACE
Write at 0x00c0000160c8 by goroutine 7:
main.increment()
/path/to/file/main.go:8 +0x44
Previous read at 0x00c0000160c8 by goroutine 6:
main.increment()
/path/to/file/main.go:8 +0x34
Goroutine 7 (running) created at:
main.main()
/path/to/file/main.go:13 +0x8e
Goroutine 6 (finished) created at:
main.main()
/path/to/file/main.go:13 +0x8e
==================
Final value: 8
Found 1 data race(s)
exit status 66
- 使用日志打印: 在关键的读写操作处添加日志,记录操作的时间、Goroutine ID等信息,通过分析日志来定位竞态。例如:
package main
import (
"fmt"
"log"
"runtime"
"sync"
)
var num int
func increment(wg *sync.WaitGroup) {
pc, _, _, _ := runtime.Caller(0)
funcName := runtime.FuncForPC(pc).Name()
log.Printf("Goroutine %d in %s about to read num\n", runtime.NumGoroutine(), funcName)
temp := num
log.Printf("Goroutine %d in %s read num as %d\n", runtime.NumGoroutine(), funcName, temp)
temp++
log.Printf("Goroutine %d in %s incremented temp to %d\n", runtime.NumGoroutine(), funcName, temp)
num = temp
log.Printf("Goroutine %d in %s set num to %d\n", runtime.NumGoroutine(), funcName, num)
wg.Done()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final value:", num)
}
解决竞态问题
- 使用
sync.Mutex
:sync.Mutex
用于保护共享资源,确保同一时间只有一个Goroutine可以访问。修改上述代码如下:
package main
import (
"fmt"
"sync"
)
var num int
var mu sync.Mutex
func increment(wg *sync.WaitGroup) {
mu.Lock()
num++
mu.Unlock()
wg.Done()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final value:", num)
}
- 使用
sync.RWMutex
: 如果读操作远多于写操作,可以使用sync.RWMutex
,它允许多个Goroutine同时读,但只允许一个Goroutine写。例如:
package main
import (
"fmt"
"sync"
)
var num int
var rwmu sync.RWMutex
func read(wg *sync.WaitGroup) {
rwmu.RLock()
fmt.Println("Read value:", num)
rwmu.RUnlock()
wg.Done()
}
func write(wg *sync.WaitGroup) {
rwmu.Lock()
num++
rwmu.Unlock()
wg.Done()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go read(&wg)
}
for i := 0; i < 3; i++ {
wg.Add(1)
go write(&wg)
}
wg.Wait()
}
在这个示例中,读操作使用RLock
和RUnlock
,写操作使用Lock
和Unlock
,这样在读多写少的场景下可以提高并发性能。