面试题答案
一键面试并发安全问题场景举例
假设我们有一个计数器,多个协程需要对其进行加一操作。如果不做任何并发控制,就会出现数据竞争,导致最终的计数值不准确。以下是示例代码:
package main
import (
"fmt"
)
var count int
func increment() {
count = count + 1
}
func main() {
var numRoutines = 1000
for i := 0; i < numRoutines; i++ {
go increment()
}
// 这里没有合适的等待机制,可能主函数先退出,协程未执行完
fmt.Println("Final count:", count)
}
在上述代码中,由于多个协程同时对count
变量进行读写操作,会导致数据竞争,每次运行程序,count
的最终值可能都不一样,且通常小于1000。
使用sync.Mutex
解决问题
我们可以使用sync.Mutex
来保护对count
变量的读写操作,确保同一时间只有一个协程能访问它。修改后的代码如下:
package main
import (
"fmt"
"sync"
)
var (
count int
mu sync.Mutex
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
count = count + 1
mu.Unlock()
}
func main() {
var numRoutines = 1000
var wg sync.WaitGroup
wg.Add(numRoutines)
for i := 0; i < numRoutines; i++ {
go increment(&wg)
}
wg.Wait()
fmt.Println("Final count:", count)
}
在这个版本中,mu.Lock()
和mu.Unlock()
之间的代码块是临界区,同一时间只有一个协程可以进入,从而保证了count
变量的读写操作的安全性。
使用sync.RWMutex
解决问题
如果读操作远远多于写操作,可以使用sync.RWMutex
。sync.RWMutex
允许多个协程同时进行读操作,但写操作时会独占锁,防止其他读写操作。假设我们有一个场景,除了上述的写操作(increment
函数),还有一个读操作函数getCount
。示例代码如下:
package main
import (
"fmt"
"sync"
)
var (
count int
mu sync.RWMutex
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
count = count + 1
mu.Unlock()
}
func getCount() int {
mu.RLock()
defer mu.RUnlock()
return count
}
func main() {
var numRoutines = 1000
var wg sync.WaitGroup
wg.Add(numRoutines)
for i := 0; i < numRoutines; i++ {
go increment(&wg)
}
wg.Wait()
fmt.Println("Final count:", getCount())
}
在getCount
函数中,使用mu.RLock()
和mu.RUnlock()
来进行读锁定,允许多个协程同时读。而在increment
函数中,仍然使用mu.Lock()
和mu.Unlock()
进行写锁定,保证写操作的原子性。