Python多进程处理嵌套对象实现遗传算法2D平台游戏种群并行更新问题
问题根本原因
- Python多进程默认进程隔离,你直接将
Player对象传给子进程时,仅会序列化对象本身的属性,不会同步主进程的全局变量。你的Player.update方法依赖全局的groups.floor_tiles、settings等变量,子进程内没有初始化这些全局资源,自然会出现属性缺失、空对象报错。 multiprocessing.Array仅支持存储C兼容的基础类型(int、float等),不能直接存放自定义Python对象,你强制传入对象类型就会触发类型报错。- 你定义的
update_players需要接收ret_queue参数,但创建进程时没有传这个参数,本身就存在传参不匹配的问题。
可行解决方案
优先用队列做结果回传,不需要折腾复杂的共享内存。遗传算法的种群更新本身就是跑完一轮再统一回收所有个体状态,序列化开销完全可控,改造成本最低。
步骤1:修改update_players函数
移除全局变量依赖,所有需要的资源都通过参数显式传入,处理完批次后统一返回结果:
def update_players(players_split, floor_tiles, config, goal, ret_queue): updated = [] for p in players_split: if not p.is_dead and not p.reached_goal and not p.finished: if p.is_ai: p.releaseLeft() p.releaseRight() if p.brain_step >= len(p.brain.instructions): if not p.reached_goal and not p.isMoving(): p.finished = True else: p.executeNextBrainStep() p.rect.y += p.y_spd p.y_spd += p.gravity for tile in floor_tiles: if helpers.rectsColliding(p.rect, tile.rect): # 垂直碰撞检测 if p.rect.getCenterY() < tile.rect.y: p.y_spd = 0 p.rect.y = tile.rect.y - p.rect.height + 1 p.is_jumping = False elif p.rect.getCenterY() > (tile.rect.y + tile.rect.height): p.y_spd = 0 p.rect.y = tile.rect.y + tile.rect.height # 水平碰撞检测 if (p.rect.getCenterY() >= tile.rect.getCenterY() - tile.rect.height/3 and p.rect.getCenterY() <= tile.rect.getCenterY() + tile.rect.height/3): if p.rect.getCenterX() < tile.rect.x: p.right = 0 p.rect.x = tile.rect.x - p.rect.width elif p.rect.getCenterX() > (tile.rect.x + tile.rect.width): p.left = 0 p.rect.x = tile.rect.x + tile.rect.width p.dir = p.right - p.left p.rect.x += p.dir * p.walk_spd # 死亡判定 if p.rect.x > config.SCR_W or (p.rect.x + p.rect.width) < 0: p.is_dead = True p.finished = True if p.rect.y > config.SCR_H or (p.rect.y + p.rect.height) < 0: p.is_dead = True p.finished = True # 最优距离判定 if config.OPTIMIZATION_FITNESS: self_dist = helpers.dist_modular(p.rect.x, goal.rect.x, p.rect.y, goal.rect.y) best_dist = helpers.dist_modular(config.BEST_X, goal.rect.x, config.BEST_Y, goal.rect.y) if self_dist < best_dist: p.finished = True p.reached_goal = True # 终点判定 if helpers.objectsColliding(p, goal): p.reached_goal = True p.finished = True updated.append(p) ret_queue.put(updated)
步骤2:修改主进程调用逻辑
Windows系统必须把多进程启动逻辑放在if __name__ == '__main__'块下,否则会反复启动进程报错:
import multiprocessing as mp import numpy as np if __name__ == '__main__': # 提前提取所有子进程需要的资源,避免依赖全局变量 all_players = groups.players_group floor_tiles = groups.floor_tiles config = settings goal = settings.goal splits = np.array_split(all_players, config.PROCESSES) ret_queue = mp.Queue() processes = [] for split in splits: p = mp.Process( target=update_players, args=(split, floor_tiles, config, goal, ret_queue) ) processes.append(p) p.start() # 回收所有更新后的玩家对象 updated_players = [] for _ in range(config.PROCESSES): updated_players.extend(ret_queue.get()) # 等待所有进程退出 for p in processes: p.join() # 替换原种群为更新后的结果 groups.players_group = updated_players
性能优化可选方案
如果后续Player对象过大、序列化开销太高,再考虑共享内存方案:把所有玩家的状态字段拆成独立的基础类型共享数组(比如所有玩家的x坐标存在一个Array('i', 玩家总数)里,y坐标存在另一个里,is_dead标记存在布尔数组里),子进程直接修改共享数组的对应位置,最后主进程再把数组值同步回Player对象即可,这个方案性能更高但改造成本更大,队列方案够用的情况下不需要优先考虑。
内容的提问来源于stack exchange,提问作者Santy Arellano
相关产品推荐
相关产品推荐

