MST
星途 面试题库

面试题:Go语言中使用channel实现Goroutine间的同步与数据竞争预防

在Go语言中,除了锁机制,channel也可用于Goroutine间的同步与数据共享。请设计一个复杂的场景,其中包含多个Goroutine,它们需要通过channel来安全地共享和修改数据,同时要确保不会出现数据竞争。描述你的设计思路,并给出完整的代码实现。
32.5万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 数据结构定义:定义一个结构体来表示要共享的数据,这里以一个简单的计数器结构体为例,包含一个计数值字段。
  2. Goroutine 职责
    • 多个 incrementer Goroutine 负责增加计数器的值。
    • 一个 printer Goroutine 负责定期打印计数器的值。
  3. Channel 使用
    • 使用一个无缓冲 channel 来传递对共享数据的操作请求。这样可以保证操作的顺序性,避免数据竞争。
    • 操作请求可以是一个函数类型,在接收端执行这些函数,从而安全地修改共享数据。

代码实现

package main

import (
    "fmt"
    "sync"
    "time"
)

// Counter 定义计数器结构体
type Counter struct {
    value int
}

// Operation 定义对 Counter 的操作类型
type Operation func(*Counter)

// increment 增加计数器的值
func increment(c *Counter) {
    c.value++
}

// print 打印计数器的值
func print(c *Counter) {
    fmt.Printf("Current value: %d\n", c.value)
}

func main() {
    counter := Counter{}
    operationChan := make(chan Operation)
    var wg sync.WaitGroup

    // 启动多个 incrementer Goroutine
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for {
                operationChan <- increment
                time.Sleep(100 * time.Millisecond)
            }
        }()
    }

    // 启动 printer Goroutine
    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            operationChan <- print
            time.Sleep(500 * time.Millisecond)
        }
    }()

    // 模拟运行一段时间后退出
    go func() {
        time.Sleep(2 * time.Second)
        close(operationChan)
    }()

    wg.Wait()
}

上述代码通过 operationChan 这个 channel 来传递对 Counter 的操作,无论是增加计数值还是打印计数值,都通过这个 channel 顺序执行,从而避免了数据竞争。incrementer Goroutine 定期向 channel 发送增加操作,printer Goroutine 定期向 channel 发送打印操作,主函数在运行一段时间后关闭 channel,所有 Goroutine 结束运行。