MST

星途 面试题库

面试题:Go语言中如何通过原子操作和channel来预防Goroutine的数据竞争并设计高并发安全的系统

设计一个高并发系统,其中多个Goroutine需要对共享资源进行操作,要求尽可能避免数据竞争。请结合原子操作(如`atomic`包中的函数)和channel,详细描述你的设计思路,并给出完整的代码实现。此外,说明在这种设计中如何处理资源的分配与回收,以确保系统的稳定性和高效性。
17.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 原子操作:使用atomic包中的函数来对共享资源进行原子性的读写操作,这样可以避免数据竞争。例如,对于整数类型的共享资源,可以使用atomic.AddInt64atomic.LoadInt64等函数。
  2. Channel:通过channel来协调Goroutine之间的通信和同步。可以将对共享资源的操作封装成消息,通过channel发送给专门处理这些操作的Goroutine,这样可以保证操作的顺序性,进一步避免数据竞争。
  3. 资源分配与回收
    • 分配:可以在初始化时预先分配一定数量的资源,并将这些资源的标识通过channel发送给需要使用资源的Goroutine。
    • 回收:当Goroutine使用完资源后,通过另一个channel将资源标识发送回资源管理Goroutine,以便再次分配。

代码实现

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

// 共享资源结构体
type Resource struct {
    id int64
    // 其他资源相关字段
}

// 资源池
type ResourcePool struct {
    resources chan *Resource
    counter   int64
}

// 创建资源池
func NewResourcePool(size int) *ResourcePool {
    pool := &ResourcePool{
        resources: make(chan *Resource, size),
        counter:   0,
    }
    for i := 0; i < size; i++ {
        pool.resources <- &Resource{id: atomic.AddInt64(&pool.counter, 1)}
    }
    return pool
}

// 获取资源
func (p *ResourcePool) GetResource() *Resource {
    return <-p.resources
}

// 释放资源
func (p *ResourcePool) ReleaseResource(r *Resource) {
    p.resources <- r
}

func main() {
    var wg sync.WaitGroup
    pool := NewResourcePool(5)

    // 模拟多个Goroutine操作资源
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            resource := pool.GetResource()
            defer pool.ReleaseResource(resource)
            // 对资源进行操作
            fmt.Printf("Goroutine using resource %d\n", resource.id)
        }()
    }

    wg.Wait()
}

资源分配与回收说明

  1. 资源分配NewResourcePool函数在初始化时,创建了一定数量的Resource实例,并将它们放入resources channel中。GetResource函数从这个channel中获取资源,实现资源的分配。
  2. 资源回收ReleaseResource函数将使用完的资源重新放回resources channel中,以便其他Goroutine再次获取,实现资源的回收。通过这种方式,确保了资源的稳定分配与回收,提高了系统的高效性。同时,使用原子操作和channel避免了数据竞争,保证了系统在高并发场景下的稳定性。