面试题答案
一键面试挑战描述
- 竞态条件:多个并发 goroutine 同时访问和修改共享变量时,由于执行顺序的不确定性,可能导致最终结果的不可预测性。例如,两个 goroutine 同时读取一个变量,然后分别进行修改并写回,最终变量的值取决于哪个 goroutine 最后完成写操作,这与预期可能不符。
- 变量生命周期管理:在并发环境下,变量何时创建、何时销毁变得复杂。如果一个变量被多个 goroutine 引用,当其中一个 goroutine 试图销毁它时,可能其他 goroutine 还在使用,从而导致程序崩溃或未定义行为。
解决方案
- 互斥锁(Mutex)
- 实现原理:通过
sync.Mutex
类型的变量来保护共享资源。在访问共享变量前,使用Lock
方法加锁,确保同一时间只有一个 goroutine 能访问共享变量,访问结束后使用Unlock
方法解锁。 - 代码示例:
- 实现原理:通过
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 < 10; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
- **应用场景**:适用于读写操作都需要保护共享变量的场景,例如银行账户的存款和取款操作。
2. 读写锁(RWMutex)
- 实现原理:sync.RWMutex
允许在同一时间有多个读操作并发执行,但写操作必须是独占的。读操作使用 RLock
和 RUnlock
方法,写操作使用 Lock
和 Unlock
方法。
- 代码示例:
package main
import (
"fmt"
"sync"
)
var (
data int
rwmu sync.RWMutex
)
func read(wg *sync.WaitGroup) {
defer wg.Done()
rwmu.RLock()
fmt.Println("Read value:", data)
rwmu.RUnlock()
}
func write(wg *sync.WaitGroup) {
defer wg.Done()
rwmu.Lock()
data++
fmt.Println("Write value:", data)
rwmu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
if i%2 == 0 {
wg.Add(1)
go write(&wg)
} else {
wg.Add(1)
go read(&wg)
}
}
wg.Wait()
}
- **应用场景**:适用于读操作远远多于写操作的场景,如缓存数据的读取和更新。读操作时可以并发进行,提高效率,写操作时保证数据一致性。
3. 通道(Channel) - 实现原理:通过通道在 goroutine 之间传递数据,避免直接共享变量。一个 goroutine 向通道发送数据,另一个 goroutine 从通道接收数据,从而实现数据的安全传递。 - 代码示例:
package main
import (
"fmt"
"sync"
)
func sendData(ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
func receiveData(ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
for val := range ch {
fmt.Println("Received:", val)
}
}
func main() {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(2)
go sendData(ch, &wg)
go receiveData(ch, &wg)
wg.Wait()
}
- **应用场景**:适用于数据在不同 goroutine 之间流动和处理的场景,如生产者 - 消费者模型。生产者将数据发送到通道,消费者从通道中获取数据进行处理,避免了共享变量带来的竞态问题。