AI Agent的并发控制:从锁到乐观并发

AI Agent的并发控制:从锁到乐观并发 当多个Agent同时操作共享资源时,并发控制成为系统正确性的基石。传统分布式系统的并发控制理论在Agent场景下需要重新审视——Agent的决策具有不确定性,操作粒度更大,冲突模式也更复杂。 问题场景 考虑一个多Agent协作的代码编辑场景:Agent A正在修改函数foo的实现,Agent B同时在为foo添加测试用例。如果A的修改改变了foo的接口,B的测试用例可能基于过时的接口而失效。这种"写-写冲突"在Agent系统中非常常见。 另一种场景是"读-写冲突":Agent C需要读取系统当前状态来做决策,但此时Agent D正在修改状态。C可能基于不一致的状态做出错误决策。 锁机制:悲观的保守派 最直接的并发控制方案是加锁。当Agent需要修改共享资源时,先获取锁,操作完成后再释放。 细粒度锁 粗粒度锁(如全局锁)实现简单但并发度低。我们采用了细粒度的资源级锁——每个共享资源有独立的锁,Agent只锁定它要操作的资源。 锁的粒度设计是关键。太粗则并发度低,太细则锁管理开销大且容易死锁。我们的经验是按"逻辑单元"加锁——在代码编辑场景中,一个函数或一个文件是一个锁单元;在知识库场景中,一个概念节点是一个锁单元。 死锁检测 多锁场景下死锁几乎不可避免。我们实现了基于等待图的死锁检测算法:周期性构建Agent间的等待关系图,检测环的存在。发现死锁后,选择优先级最低的Agent回滚其操作。 锁的代价 锁机制的主要代价是等待。在高并发场景下,Agent可能花费大量时间等待锁,严重降低系统吞吐量。更糟糕的是,Agent的推理过程是长耗时操作(通常秒级),持有锁的时间远长于传统数据库事务,这使得锁争用问题更加严重。 乐观并发控制 乐观并发控制(OCC)的核心假设:冲突是稀少的,所以不如先执行操作,提交时再检查冲突。 版本戳机制 我们为每个共享资源维护一个版本号。Agent在读取资源时记录版本号,在提交修改时检查版本是否变化。如果版本变了,说明其他Agent在此期间修改了该资源,当前Agent需要重新基于最新状态执行。 OCC的优势在于不阻塞读操作——Agent可以自由读取任何资源,只在写提交时才检查冲突。这对于"读多写少"的Agent场景非常合适。 冲突解决 当乐观并发检测到冲突时,如何解决?我们实现了三种策略: 自动重试:Agent基于最新状态重新执行整个操作。适用于确定性操作——同样的输入会产生同样的输出。但Agent的推理具有随机性,重试可能产生不同结果,需要在业务层面确保等价性。 三方仲裁:引入第三个Agent(仲裁者)来决定如何合并冲突的修改。适用于修改可以合并的场景,如两个Agent分别添加了不同的注释。 人类介入:对于无法自动解决的冲突,暂停相关Agent并请求人类决策。这是最后手段,在实践中约5%的冲突需要人类介入。 混合策略 纯悲观和纯乐观都不是最优解。我们采用了混合策略: 写操作:使用悲观锁,因为写冲突的回滚代价高 读操作:使用乐观读,不阻塞但提交时验证 长事务:分段提交,每段内使用乐观并发,段间使用锁 这种混合策略在我们的测试中将系统吞吐量提升了约3倍,同时保证了正确性。 Agent特有的挑战 Agent并发控制有一些传统分布式系统中不存在的独特挑战: 不确定性:同样的输入,Agent可能做出不同的决策。这使得"重放"策略——通过重新执行来解决冲突——变得不可靠。 长事务:Agent的一个任务可能持续数分钟甚至数小时。如此长的事务在传统并发控制中是噩梦——锁持有时间过长或验证窗口过大。 语义冲突:两个Agent的修改在语法上不冲突,但在语义上矛盾。例如Agent A将变量名从"count"改为"total",Agent B在新代码中使用了"count"。语法上没有文本冲突,但语义上代码已经断裂。检测语义冲突需要模型层面的理解能力。 结语 并发控制是Agent系统从单机走向分布式的必经之路。传统分布式系统的经验为我们提供了基础框架,但Agent的特殊性要求我们对这些框架进行改造和扩展。未来的研究方向包括基于语义的冲突检测和Agent行为预测——如果我们能预测Agent将要做什么,就可以提前规避冲突。 本文同步发布于 硅基AGI论坛

2026-07-12 · 1 min · 46 words · 硅基 AGI 探索者

AI Agent的并发控制:从锁到乐观并发

AI Agent的并发控制:从锁到乐观并发 当多个Agent同时操作共享资源时,并发控制成为系统正确性的基石。传统分布式系统的并发控制理论在Agent场景下需要重新审视——Agent的决策具有不确定性,操作粒度更大,冲突模式也更复杂。 问题场景 考虑一个多Agent协作的代码编辑场景:Agent A正在修改函数foo的实现,Agent B同时在为foo添加测试用例。如果A的修改改变了foo的接口,B的测试用例可能基于过时的接口而失效。这种"写-写冲突"在Agent系统中非常常见。 另一种场景是"读-写冲突":Agent C需要读取系统当前状态来做决策,但此时Agent D正在修改状态。C可能基于不一致的状态做出错误决策。 锁机制:悲观的保守派 最直接的并发控制方案是加锁。当Agent需要修改共享资源时,先获取锁,操作完成后再释放。 细粒度锁 粗粒度锁(如全局锁)实现简单但并发度低。我们采用了细粒度的资源级锁——每个共享资源有独立的锁,Agent只锁定它要操作的资源。 锁的粒度设计是关键。太粗则并发度低,太细则锁管理开销大且容易死锁。我们的经验是按"逻辑单元"加锁——在代码编辑场景中,一个函数或一个文件是一个锁单元;在知识库场景中,一个概念节点是一个锁单元。 死锁检测 多锁场景下死锁几乎不可避免。我们实现了基于等待图的死锁检测算法:周期性构建Agent间的等待关系图,检测环的存在。发现死锁后,选择优先级最低的Agent回滚其操作。 锁的代价 锁机制的主要代价是等待。在高并发场景下,Agent可能花费大量时间等待锁,严重降低系统吞吐量。更糟糕的是,Agent的推理过程是长耗时操作(通常秒级),持有锁的时间远长于传统数据库事务,这使得锁争用问题更加严重。 乐观并发控制 乐观并发控制(OCC)的核心假设:冲突是稀少的,所以不如先执行操作,提交时再检查冲突。 版本戳机制 我们为每个共享资源维护一个版本号。Agent在读取资源时记录版本号,在提交修改时检查版本是否变化。如果版本变了,说明其他Agent在此期间修改了该资源,当前Agent需要重新基于最新状态执行。 OCC的优势在于不阻塞读操作——Agent可以自由读取任何资源,只在写提交时才检查冲突。这对于"读多写少"的Agent场景非常合适。 冲突解决 当乐观并发检测到冲突时,如何解决?我们实现了三种策略: 自动重试:Agent基于最新状态重新执行整个操作。适用于确定性操作——同样的输入会产生同样的输出。但Agent的推理具有随机性,重试可能产生不同结果,需要在业务层面确保等价性。 三方仲裁:引入第三个Agent(仲裁者)来决定如何合并冲突的修改。适用于修改可以合并的场景,如两个Agent分别添加了不同的注释。 人类介入:对于无法自动解决的冲突,暂停相关Agent并请求人类决策。这是最后手段,在实践中约5%的冲突需要人类介入。 混合策略 纯悲观和纯乐观都不是最优解。我们采用了混合策略: 写操作:使用悲观锁,因为写冲突的回滚代价高 读操作:使用乐观读,不阻塞但提交时验证 长事务:分段提交,每段内使用乐观并发,段间使用锁 这种混合策略在我们的测试中将系统吞吐量提升了约3倍,同时保证了正确性。 Agent特有的挑战 Agent并发控制有一些传统分布式系统中不存在的独特挑战: 不确定性:同样的输入,Agent可能做出不同的决策。这使得"重放"策略——通过重新执行来解决冲突——变得不可靠。 长事务:Agent的一个任务可能持续数分钟甚至数小时。如此长的事务在传统并发控制中是噩梦——锁持有时间过长或验证窗口过大。 语义冲突:两个Agent的修改在语法上不冲突,但在语义上矛盾。例如Agent A将变量名从"count"改为"total",Agent B在新代码中使用了"count"。语法上没有文本冲突,但语义上代码已经断裂。检测语义冲突需要模型层面的理解能力。 结语 并发控制是Agent系统从单机走向分布式的必经之路。传统分布式系统的经验为我们提供了基础框架,但Agent的特殊性要求我们对这些框架进行改造和扩展。未来的研究方向包括基于语义的冲突检测和Agent行为预测——如果我们能预测Agent将要做什么,就可以提前规避冲突。 本文同步发布于 硅基AGI论坛

2026-07-12 · 1 min · 46 words · 硅基 AGI 探索者
agent concurrency control

Agent 并发控制:从单线程到分布式锁

引言 Agent 不是孤立的——多个用户同时请求、多个工具并行执行、多个 Agent 协作完成任务。并发控制确保这些并行活动不互相干扰、不超出资源限制、不产生数据竞争。2026年,随着 Agent 集群规模扩大到数百实例,并发控制从单机问题升级为分布式问题。 一、Agent 并发的四个层次 ┌──────────────────────────────────────────────────┐ │ Agent 并发控制层次 │ ├──────────────────┬───────────────────────────────┤ │ 请求级 │ 多用户并发请求 │ │ (Request) │ → 限流 + 队列 + 优先级 │ ├──────────────────┼───────────────────────────────┤ │ Agent级 │ 单用户多Agent实例 │ │ (Agent) │ → 信号量 + 状态隔离 │ ├──────────────────┼───────────────────────────────┤ │ 工具级 │ 多工具并行调用 │ │ (Tool) │ → 并发限制 + 超时控制 │ ├──────────────────┼───────────────────────────────┤ │ 资源级 │ 共享资源访问 │ │ (Resource) │ → 分布式锁 + 乐观锁 │ └──────────────────┴───────────────────────────────┘ 二、请求级并发控制 2.1 多维限流器 import asyncio import time from collections import defaultdict class MultiDimensionRateLimiter: """多维度限流器""" def __init__(self, redis_client): self.redis = redis_client async def check( self, user_id: str, agent_name: str, limits: dict ) -> bool: """多维度限流检查 limits = { "qps": 10, # 每秒请求数 "concurrent": 5, # 最大并发 "daily_tokens": 500000, # 日Token上限 "monthly_cost": 100, # 月成本上限 } """ checks = [] # QPS 限流(滑动窗口) if "qps" in limits: checks.append(self._check_sliding_window( f"qps:{user_id}:{agent_name}", limits["qps"], window=1 )) # 并发限流 if "concurrent" in limits: checks.append(self._check_concurrent( f"conc:{user_id}:{agent_name}", limits["concurrent"] )) # Token 配额 if "daily_tokens" in limits: checks.append(self._check_quota( f"tokens:{user_id}:{date.today()}", limits["daily_tokens"] )) results = await asyncio.gather(*checks) return all(results) async def _check_sliding_window( self, key: str, limit: int, window: int ) -> bool: """滑动窗口限流""" now = time.time() pipe = self.redis.pipeline() # 移除窗口外的记录 pipe.zremrangebyscore(key, 0, now - window) # 添加当前请求 pipe.zadd(key, {str(uuid.uuid4()): now}) # 统计窗口内请求数 pipe.zcard(key) # 设置过期时间 pipe.expire(key, window * 2) results = await pipe.execute() count = results[2] if count > limit: # 移除刚添加的记录 pipe.zrem(key, str(uuid.uuid4())) return False return True async def _check_concurrent(self, key: str, limit: int) -> bool: """并发数限流""" current = await self.redis.incr(key) if current == 1: await self.redis.expire(key, 300) # 5分钟自动过期 if current > limit: await self.redis.decr(key) return False return True async def release_concurrent(self, key: str): """释放并发槽""" await self.redis.decr(key) class PriorityRequestQueue: """优先级请求队列""" def __init__(self, max_concurrent: int = 100): self.semaphore = asyncio.Semaphore(max_concurrent) self.queues = { "high": asyncio.Queue(maxsize=50), "normal": asyncio.Queue(maxsize=500), "low": asyncio.Queue(maxsize=1000), } self._running = True async def submit( self, request: Request, priority: str = "normal" ) -> Response: """提交请求""" queue = self.queues.get(priority, self.queues["normal"]) if queue.full(): raise QueueFullError(f"{priority} queue is full") future = asyncio.Future() await queue.put((request, future)) # 启动消费者(如果未启动) asyncio.create_task(self._consume()) return await future async def _consume(self): """消费请求:按优先级调度""" async with self.semaphore: # 按优先级获取请求 for priority in ["high", "normal", "low"]: queue = self.queues[priority] if not queue.empty(): request, future = await queue.get() try: result = await self._process(request) future.set_result(result) except Exception as e: future.set_exception(e) return # 所有队列为空 await asyncio.sleep(0.01) 三、工具级并发控制 3.1 并行工具调用管理 class ParallelToolExecutor: """并行工具调用管理器""" def __init__(self): self.tool_limits = {} # {tool_name: Semaphore} self.global_limit = asyncio.Semaphore(10) def register_tool( self, name: str, max_concurrent: int = 5, timeout: float = 30.0 ): """注册工具并发限制""" self.tool_limits[name] = { "semaphore": asyncio.Semaphore(max_concurrent), "timeout": timeout } async def execute_parallel( self, tool_calls: list[ToolCall] ) -> list[ToolResult]: """并行执行多个工具调用""" tasks = [] for call in tool_calls: task = asyncio.create_task( self._execute_single(call) ) tasks.append(task) # 等待所有完成,设置全局超时 results = await asyncio.gather(*tasks, return_exceptions=True) return [ result if not isinstance(result, Exception) else ToolResult(success=False, error=str(result)) for result in results ] async def _execute_single(self, call: ToolCall) -> ToolResult: """执行单个工具调用(带并发限制和超时)""" tool_limit = self.tool_limits.get(call.tool) if not tool_limit: raise UnknownToolError(call.tool) # 双重信号量:全局 + 工具级 async with self.global_limit: async with tool_limit["semaphore"]: try: result = await asyncio.wait_for( self._call_tool(call), timeout=tool_limit["timeout"] ) return ToolResult(success=True, data=result) except asyncio.TimeoutError: return ToolResult( success=False, error=f"Tool {call.tool} timed out" ) # 注册工具并发限制 executor = ParallelToolExecutor() executor.register_tool("web_search", max_concurrent=5, timeout=10) executor.register_tool("database_query", max_concurrent=3, timeout=15) executor.register_tool("file_read", max_concurrent=20, timeout=5) executor.register_tool("file_write", max_concurrent=2, timeout=10) executor.register_tool("send_email", max_concurrent=1, timeout=30) 四、资源级并发控制 4.1 分布式锁 class DistributedLock: """基于 Redis 的分布式锁""" def __init__(self, redis_client): self.redis = redis_client self._local_locks = {} # 本地锁缓存 async def acquire( self, resource: str, holder: str, ttl: int = 30, timeout: float = 10.0 ) -> bool: """获取分布式锁 Args: resource: 锁定的资源名 holder: 持有者标识(如 agent_id + session_id) ttl: 锁的生存时间(秒) timeout: 等待获取锁的超时时间 """ lock_key = f"lock:{resource}" start = time.time() while time.time() - start < timeout: # 尝试获取锁(原子操作) acquired = await self.redis.set( lock_key, holder, nx=True, ex=ttl ) if acquired: # 启动看门狗续期 asyncio.create_task(self._watchdog(resource, holder, ttl)) return True # 检查持有者是否是自己(重入) current = await self.redis.get(lock_key) if current == holder: await self.redis.expire(lock_key, ttl) return True # 等待重试 await asyncio.sleep(0.1) return False async def release(self, resource: str, holder: str) -> bool: """释放锁(使用 Lua 脚本保证原子性)""" lua_script = """ if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end """ result = await self.redis.eval( lua_script, 1, f"lock:{resource}", holder ) return bool(result) async def _watchdog(self, resource: str, holder: str, ttl: int): """看门狗:自动续期锁""" lock_key = f"lock:{resource}" renew_interval = ttl * 0.3 # 在30% TTL时续期 while True: await asyncio.sleep(renew_interval) # 检查是否仍持有锁 current = await self.redis.get(lock_key) if current != holder: break # 锁已被释放或被抢 # 续期 await self.redis.expire(lock_key, ttl) class AgentResourceLock: """Agent 资源锁管理""" def __init__(self, lock_manager: DistributedLock): self.locks = lock_manager async def with_resource_lock( self, resource: str, agent_id: str, action: callable, ttl: int = 30 ): """在锁保护下执行操作""" acquired = await self.locks.acquire( resource, agent_id, ttl=ttl ) if not acquired: raise LockAcquisitionError( f"Could not acquire lock for {resource}" ) try: return await action() finally: await self.locks.release(resource, agent_id) 4.2 乐观锁 class OptimisticLock: """乐观锁:适用于低冲突场景""" async def update_with_version( self, resource_id: str, update_fn: callable, max_retries: int = 3 ) -> any: """带版本控制的更新""" for attempt in range(max_retries): # 1. 读取当前版本 current = await self.db.get(resource_id) if not current: raise NotFoundError(resource_id) version = current.version # 2. 计算更新 updated = await update_fn(current.data) # 3. 乐观写入(带版本检查) result = await self.db.update_with_version( resource_id, data=updated, expected_version=version ) if result.success: return updated # 版本冲突,重试 logger.warning( f"Optimistic lock conflict on {resource_id}, " f"attempt {attempt+1}" ) await asyncio.sleep(0.05 * attempt) # 短暂退避 raise ConcurrencyError( f"Failed after {max_retries} retries due to conflicts" ) 五、Agent 级并发控制 class AgentInstanceManager: """Agent 实例管理——控制单用户的 Agent 并发""" def __init__(self, redis_client): self.redis = redis_client self.limits = { "free": 1, # 免费用户:1个并发Agent "pro": 5, # Pro用户:5个 "enterprise": 20, # 企业用户:20个 } async def acquire_slot( self, user_id: str, tier: str, agent_type: str ) -> AgentSlot: """获取 Agent 执行槽""" limit = self.limits.get(tier, 1) key = f"agent_slots:{user_id}" # 检查当前活跃数 active = await self.redis.scard(key) if active >= limit: raise ConcurrencyLimitError( f"User {user_id} has {active}/{limit} active agents" ) # 分配槽位 slot_id = str(uuid.uuid4()) await self.redis.sadd(key, slot_id) await self.redis.expire(key, 3600) # 1小时过期 return AgentSlot( slot_id=slot_id, user_id=user_id, agent_type=agent_type, acquired_at=time.time() ) async def release_slot(self, slot: AgentSlot): """释放 Agent 执行槽""" key = f"agent_slots:{slot.user_id}" await self.redis.srem(key, slot.slot_id) class AgentCoordinator: """Agent 协调器——管理多Agent并发执行""" async def run_agents_concurrent( self, agents: list[Agent], max_parallel: int = 5 ) -> list[AgentResult]: """并发运行多个 Agent""" semaphore = asyncio.Semaphore(max_parallel) async def run_one(agent: Agent) -> AgentResult: async with semaphore: try: result = await agent.run() return AgentResult( agent_id=agent.id, success=True, result=result ) except Exception as e: return AgentResult( agent_id=agent.id, success=False, error=str(e) ) return await asyncio.gather(*[run_one(a) for a in agents]) async def run_agents_pipeline( self, agents: list[Agent], dependencies: dict # {agent_id: [depends_on_ids]} ) -> dict[str, AgentResult]: """按依赖关系运行 Agent(拓扑排序)""" results = {} completed = set() while len(completed) < len(agents): # 找到可执行的 Agent(依赖已完成) ready = [ agent for agent in agents if agent.id not in completed and all( dep in completed for dep in dependencies.get(agent.id, []) ) ] if not ready: # 检测死锁 raise DeadlockError("Dependency cycle detected") # 并发执行就绪的 Agent batch_results = await self.run_agents_concurrent(ready) for result in batch_results: results[result.agent_id] = result completed.add(result.agent_id) return results 六、并发控制监控 class ConcurrencyMonitor: """并发控制监控""" METRICS = { "active_requests": "当前活跃请求数", "queue_depth": "各优先级队列深度", "concurrent_agents": "各用户活跃Agent数", "tool_concurrent_usage": "各工具并发使用数", "lock_wait_time": "锁等待时间分布", "lock_contention_rate": "锁竞争率", "rate_limit_hits": "限流触发次数", } async def health_check(self) -> dict: return { "healthy": await self._check_health(), "active_connections": await self._count_active(), "queue_health": await self._check_queues(), "lock_health": await self._check_locks(), "alerts": await self._get_alerts(), } async def detect_issues(self) -> list[Issue]: issues = [] # 检测队列堆积 for priority, queue in self.queues.items(): if queue.qsize() > queue.maxsize * 0.8: issues.append(Issue( severity="warning", type="queue_backlog", detail=f"{priority} queue at {queue.qsize()}/{queue.maxsize}" )) # 检测锁饥饿 lock_waits = await self._get_lock_wait_times() for resource, wait_time in lock_waits.items(): if wait_time > 5.0: issues.append(Issue( severity="high", type="lock_starvation", detail=f"{resource} lock wait: {wait_time:.1f}s" )) # 检测限流频繁触发 rate_limit_hits = await self._get_rate_limit_hits() for user_id, hits in rate_limit_hits.items(): if hits > 100: # 每分钟超过100次 issues.append(Issue( severity="medium", type="excessive_rate_limiting", detail=f"User {user_id} hit rate limit {hits} times" )) return issues 七、并发控制 Checklist □ 多维限流(QPS + 并发数 + Token + 成本) □ 优先级队列(高/中/低优先级请求隔离) □ 工具级并发限制(不同工具有不同并发上限) □ 全局并发限制(总并发不超过系统容量) □ 分布式锁保护共享资源(带看门狗续期) □ 乐观锁用于低冲突场景 □ Agent 实例数按用户等级限制 □ 依赖感知的 Agent 调度(拓扑排序) □ 死锁检测和预防 □ 并发监控和告警 □ 队列堆积自动扩容 □ 压测验证并发上限 结语 并发控制是 Agent 系统从"能跑"到"能扛"的关键。单机时代,一个信号量就够了;分布式时代,你需要考虑锁的公平性、可重入性、死锁预防和故障恢复。最好的并发控制是"恰到好处"——限制太松会导致雪崩,限制太紧会浪费资源。通过压测找到你的系统边界,然后在那里设置护栏。记住:并发控制不是阻碍性能,而是保护性能。 加入讨论 这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。 ...

2026-06-28 · 7 min · 1288 words · 硅基 AGI 探索者
鲁ICP备2026018361号