MST

星途 面试题库

面试题:Go接口方法调用的边界控制 - 并发场景下的考量

在一个高并发的Go程序中,有多个`Worker`结构体实现了`TaskExecutor`接口,`TaskExecutor`接口包含`Execute`方法。`Execute`方法会执行一些有状态的任务,并且可能会访问共享资源。请详细描述在并发场景下,如何对`Execute`方法的调用进行边界控制,以避免数据竞争、死锁等问题,同时确保任务执行的高效性和正确性。并给出相应的示例代码。
13.9万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

避免数据竞争和死锁的方法

  1. 互斥锁(Mutex):使用sync.Mutex来保护共享资源。在访问共享资源前锁定互斥锁,访问结束后解锁。这样可以确保同一时间只有一个Execute方法实例能访问共享资源,避免数据竞争。
  2. 读写锁(RWMutex):如果共享资源的读操作远多于写操作,可以使用sync.RWMutex。读操作时可以多个实例同时进行,写操作时则需要独占访问,以此提高性能。
  3. 通道(Channel):通过通道来传递任务,将任务的接收和执行分离开来。这样可以避免直接对共享资源的并发访问,同时利用Go语言的并发模型来高效处理任务。
  4. 避免死锁:死锁通常发生在多个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()
}

代码说明

  1. 接口和结构体定义:定义了TaskExecutor接口和实现该接口的Worker结构体,Worker结构体包含一个互斥锁和共享资源。
  2. Execute 方法:在Execute方法中,通过互斥锁来保护对共享资源的访问,确保同一时间只有一个实例可以修改共享资源。
  3. main 函数:创建了多个Worker实例,并使用Go协程并发执行Execute方法,通过sync.WaitGroup来等待所有任务执行完毕。

这样,通过使用互斥锁,我们有效地避免了数据竞争问题,保证了任务执行的正确性和高效性。如果场景中有更多读操作,可以考虑将sync.Mutex替换为sync.RWMutex以提高性能。同时,在更复杂的场景中,还可以结合通道来进一步优化并发控制。