面试题答案
一键面试互斥锁(Mutex)
互斥锁用于保证在同一时刻只有一个 goroutine 能够访问共享资源,从而避免资源竞争。
package main
import (
"fmt"
"sync"
)
var (
counter int
mu sync.Mutex
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
在上述代码中,mu
是一个互斥锁,在访问和修改 counter
变量前,先通过 mu.Lock()
锁定,操作完成后通过 mu.Unlock()
解锁,确保同一时间只有一个 goroutine 能修改 counter
。
读写锁(RWMutex)
读写锁适用于读多写少的场景。它允许有多个读操作同时进行,但写操作时需要独占资源。
package main
import (
"fmt"
"sync"
)
var (
data int
rwMutex sync.RWMutex
)
func read(wg *sync.WaitGroup) {
defer wg.Done()
rwMutex.RLock()
fmt.Println("Read data:", data)
rwMutex.RUnlock()
}
func write(wg *sync.WaitGroup, value int) {
defer wg.Done()
rwMutex.Lock()
data = value
fmt.Println("Write data:", data)
rwMutex.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go read(&wg)
}
for i := 0; i < 2; i++ {
wg.Add(1)
go write(&wg, i*10)
}
wg.Wait()
}
在这段代码中,读操作使用 rwMutex.RLock()
和 rwMutex.RUnlock()
,允许多个读操作并行;写操作使用 rwMutex.Lock()
和 rwMutex.Unlock()
,保证写操作时的独占性。
通道(Channel)
通道可以用于在 goroutine 之间传递数据,通过在通道上发送和接收数据来同步操作,避免直接访问共享资源。
package main
import (
"fmt"
"sync"
)
func producer(ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
func consumer(ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
for value := range ch {
fmt.Println("Consumed:", value)
}
}
func main() {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(1)
go producer(ch, &wg)
wg.Add(1)
go consumer(ch, &wg)
wg.Wait()
}
在这个例子中,producer
goroutine 通过通道 ch
向 consumer
goroutine 发送数据,consumer
通过 for... range
从通道接收数据,直到通道关闭。这样避免了共享资源的竞争。
复杂场景下的运用
假设我们有一个银行账户的场景,需要支持存款、取款和查询余额操作,并且有多个并发的操作请求。
package main
import (
"fmt"
"sync"
)
type BankAccount struct {
balance int
mu sync.RWMutex
}
func (a *BankAccount) Deposit(amount int) {
a.mu.Lock()
a.balance += amount
a.mu.Unlock()
}
func (a *BankAccount) Withdraw(amount int) bool {
a.mu.Lock()
if a.balance >= amount {
a.balance -= amount
a.mu.Unlock()
return true
}
a.mu.Unlock()
return false
}
func (a *BankAccount) Balance() int {
a.mu.RLock()
balance := a.balance
a.mu.RUnlock()
return balance
}
func main() {
account := BankAccount{}
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
account.Deposit(100)
}()
}
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
account.Withdraw(50)
}()
}
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Balance:", account.Balance())
}()
}
wg.Wait()
}
在这个复杂场景中,对于存款和取款操作,因为涉及到对余额的修改,使用互斥锁 mu.Lock()
和 mu.Unlock()
保证数据一致性;对于查询余额操作,因为只是读取数据,使用读写锁的读锁 mu.RLock()
和 mu.RUnlock()
,提高并发性能。通过这样合理运用互斥锁和读写锁机制,保证了银行账户操作在多 goroutine 并发情况下的数据一致性和性能。