面试题答案
一键面试避免竞态条件
- 使用互斥锁(Mutex):
- 当多个
goroutine
可能同时访问和修改共享资源时,使用互斥锁来保护共享资源。只有获得锁的goroutine
才能访问和修改资源,其他goroutine
需要等待锁的释放。 - 在Go语言中,
sync.Mutex
提供了这种机制。
- 当多个
- 使用读写锁(RWMutex):
- 如果共享资源主要是读操作,写操作较少,可以使用读写锁。多个
goroutine
可以同时读共享资源,但写操作时需要独占锁,以保证数据一致性。 sync.RWMutex
在Go语言中实现了读写锁的功能。
- 如果共享资源主要是读操作,写操作较少,可以使用读写锁。多个
- 避免共享可变状态:
- 尽量减少共享可变状态的使用。可以通过将数据复制到每个
goroutine
本地,避免共享资源带来的竞态问题。
- 尽量减少共享可变状态的使用。可以通过将数据复制到每个
结合channel
和goroutine
实现高效并发处理
- 使用
channel
进行通信:channel
是Go语言中用于goroutine
之间通信的机制。通过channel
,goroutine
可以安全地传递数据,而不需要使用锁来保护共享资源。- 有两种类型的
channel
:带缓冲的channel
和不带缓冲的channel
。不带缓冲的channel
在发送和接收操作时会阻塞,直到对应的接收或发送操作准备好。带缓冲的channel
可以在缓冲区未满时发送数据而不阻塞。
- 扇入(Fan - In)和扇出(Fan - Out)模式:
- 扇出:将一个任务分发到多个
goroutine
中并行执行。例如,有一个任务列表,将每个任务分配给不同的goroutine
去处理。 - 扇入:将多个
goroutine
的结果收集到一个channel
中。例如,多个goroutine
并行计算结果,然后将结果发送到同一个channel
,由一个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:", counter)
}
- 使用
channel
和goroutine
实现并发处理(扇出和扇入):
package main
import (
"fmt"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d started job %d\n", id, j)
result := j * 2
fmt.Printf("Worker %d finished job %d with result %d\n", id, j, result)
results <- result
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
const numWorkers = 3
for w := 1; w <= numWorkers; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= numJobs; a++ {
<-results
}
close(results)
}
在上述代码中:
- 第一个示例展示了如何使用互斥锁保护共享变量
counter
,避免多个goroutine
同时修改导致竞态条件。 - 第二个示例展示了如何通过
channel
实现扇出(将任务分发给多个worker
goroutine
)和扇入(收集worker
goroutine
的结果)模式。jobs
channel
用于向worker
发送任务,results
channel
用于收集worker
的计算结果。