面试题答案
一键面试避免数据竞争和死锁的方法
- 互斥锁(Mutex):使用
sync.Mutex
来保护共享资源。在访问共享资源前锁定互斥锁,访问结束后解锁。这样可以确保同一时间只有一个Execute
方法实例能访问共享资源,避免数据竞争。 - 读写锁(RWMutex):如果共享资源的读操作远多于写操作,可以使用
sync.RWMutex
。读操作时可以多个实例同时进行,写操作时则需要独占访问,以此提高性能。 - 通道(Channel):通过通道来传递任务,将任务的接收和执行分离开来。这样可以避免直接对共享资源的并发访问,同时利用Go语言的并发模型来高效处理任务。
- 避免死锁:死锁通常发生在多个
Execute
方法实例相互等待对方释放资源的情况下。为了避免死锁,应该确保所有的锁按照相同的顺序获取,并且在获取锁失败时及时释放已经获取的锁。
示例代码
package main
import (
"fmt"
"sync"
)
// TaskExecutor 接口定义
type TaskExecutor interface {
Execute()
}
// Worker 结构体实现 TaskExecutor 接口
type Worker struct {
id int
mu sync.Mutex
sharedResource int
}
// Execute 方法实现
func (w *Worker) Execute() {
// 使用互斥锁保护共享资源
w.mu.Lock()
defer w.mu.Unlock()
// 模拟有状态的任务
w.sharedResource++
fmt.Printf("Worker %d executed task, sharedResource: %d\n", w.id, w.sharedResource)
}
func main() {
var wg sync.WaitGroup
workers := make([]TaskExecutor, 5)
for i := 0; i < 5; i++ {
workers[i] = &Worker{id: i}
wg.Add(1)
go func(executor TaskExecutor) {
defer wg.Done()
executor.Execute()
}(workers[i])
}
wg.Wait()
}
代码说明
- 接口和结构体定义:定义了
TaskExecutor
接口和实现该接口的Worker
结构体,Worker
结构体包含一个互斥锁和共享资源。 - Execute 方法:在
Execute
方法中,通过互斥锁来保护对共享资源的访问,确保同一时间只有一个实例可以修改共享资源。 - main 函数:创建了多个
Worker
实例,并使用Go协程并发执行Execute
方法,通过sync.WaitGroup
来等待所有任务执行完毕。
这样,通过使用互斥锁,我们有效地避免了数据竞争问题,保证了任务执行的正确性和高效性。如果场景中有更多读操作,可以考虑将sync.Mutex
替换为sync.RWMutex
以提高性能。同时,在更复杂的场景中,还可以结合通道来进一步优化并发控制。