You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go并发网格旅行者模拟程序开发问题求助

问题根源与核心修复思路

你的程序出现的重复ID、旅行者数量异常、旅行者消失等问题,本质是数据竞争——多个goroutine同时读写共享资源(网格、旅行者状态)时未做同步控制,导致内存访问混乱。下面分点拆解解决方法:

1. Mutex的正确使用时机

所有读写共享资源的操作必须加锁,避免并发冲突:

  • 旅行者检查目标位置是否为空、更新坐标时
  • Camera读取整个网格状态时
  • 生成或修改全局旅行者ID、数量等状态时
    示例代码片段:
var gridMutex sync.Mutex

// 旅行者移动前的检查与更新逻辑
gridMutex.Lock()
defer gridMutex.Unlock()
if grid[newY][newX] == 0 {
    grid[oldY][oldX] = 0
    grid[newY][newX] = traveler.ID
    traveler.X, traveler.Y = newX, newY
}

2. WaitGroup的用法

WaitGroup用于等待所有goroutine完成:

  • 主goroutine中初始化var wg sync.WaitGroup
  • 每个旅行者goroutine启动前调用wg.Add(1)
  • 每个旅行者goroutine结束前调用wg.Done()
  • 主goroutine最后调用wg.Wait()等待所有任务结束
    如果是持续运行的模拟,可通过select{}阻塞主goroutine,无需WaitGroup。

3. Channel的合理应用

Channel适合做goroutine间的消息传递,你的场景可以这样用:

  • 收集旅行者的移动记录,供Camera打印
  • 发送停止信号给所有goroutine(比如模拟结束)
    不要用Channel传递ID表示移动意愿,直接在旅行者goroutine内处理逻辑,加锁保护共享资源即可。

4. Goroutine循环的位置

每个旅行者对应一个独立goroutine,循环放在goroutine内部——每个旅行者持续执行移动逻辑(随机等待、检查移动条件、更新位置),直到收到停止信号或模拟结束。

代码改进关键要点

  • 给Traveler结构体添加当前坐标字段,每个旅行者自行维护状态,但修改网格时必须加锁
  • 全局网格用Mutex保护,任何读写操作都要先加锁
  • Camera的定时打印逻辑需加锁读取网格,避免读到半更新的状态
  • 生成旅行者ID时用加锁的计数器确保唯一性,避免多个goroutine同时生成重复ID
简化示例代码
package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

type Traveler struct {
    ID int
    X  int
    Y  int
}

var (
    grid       [][]int
    gridMutex  sync.Mutex
    travelerWG sync.WaitGroup
)

func travelerLoop(t *Traveler) {
    defer travelerWG.Done()
    // 上下左右四个方向
    directions := [][]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}

    for {
        // 随机等待一段时间再移动
        time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)

        gridMutex.Lock()
        oldX, oldY := t.X, t.Y
        // 随机选择移动方向
        dir := directions[rand.Intn(4)]
        newX, newY := oldX+dir[0], oldY+dir[1]
        // 检查边界与目标位置是否为空
        if newX >= 0 && newX < len(grid[0]) && newY >= 0 && newY < len(grid) && grid[newY][newX] == 0 {
            grid[oldY][oldX] = 0
            grid[newY][newX] = t.ID
            t.X, t.Y = newX, newY
            fmt.Printf("旅行者%d从(%d,%d)移动到(%d,%d)\n", t.ID, oldX, oldY, newX, newY)
        }
        gridMutex.Unlock()
    }
}

func cameraLoop(interval time.Duration) {
    for {
        time.Sleep(interval)
        gridMutex.Lock()
        fmt.Println("\n当前网格状态:")
        for _, row := range grid {
            fmt.Println(row)
        }
        gridMutex.Unlock()
    }
}

func main() {
    rand.Seed(time.Now().UnixNano())
    n, m, k := 5, 5, 3 // 5x5网格,3个旅行者

    // 初始化网格
    grid = make([][]int, n)
    for i := range grid {
        grid[i] = make([]int, m)
    }

    // 生成旅行者并分配初始位置
    for id := 1; id <= k; id++ {
        var x, y int
        gridMutex.Lock()
        // 寻找空位置
        for {
            x = rand.Intn(m)
            y = rand.Intn(n)
            if grid[y][x] == 0 {
                grid[y][x] = id
                break
            }
        }
        gridMutex.Unlock()
        traveler := &Traveler{ID: id, X: x, Y: y}
        travelerWG.Add(1)
        go travelerLoop(traveler)
    }

    // 启动Camera定时打印
    go cameraLoop(2 * time.Second)

    // 阻塞主goroutine,避免程序退出
    select {}
}

内容的提问来源于stack exchange,提问作者Wojtek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 15:17:17