面试题答案
一键面试使用 asyncio.Lock
解决资源竞争问题
在 asyncio
中,asyncio.Lock
是一个异步锁,用于防止多个协程同时访问共享资源。以下是具体的代码示例:
import asyncio
# 共享资源
shared_dict = {}
lock = asyncio.Lock()
async def update_shared_dict(key, value):
async with lock:
shared_dict[key] = value
await asyncio.sleep(0.1) # 模拟一些异步操作
print(f"Updated shared_dict with key {key} and value {value}")
async def main():
tasks = []
for i in range(5):
task = asyncio.create_task(update_shared_dict(f"key_{i}", i))
tasks.append(task)
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main())
在上述代码中:
- 定义了一个全局字典
shared_dict
作为共享资源。 - 创建了一个
asyncio.Lock
实例lock
。 update_shared_dict
函数使用async with lock
语句来获取锁,在获取锁后,对共享字典进行操作,操作完成后自动释放锁。main
函数创建多个任务并发调用update_shared_dict
函数。
使用锁机制在高并发场景下的性能开销及应对策略
性能开销
- 等待时间增加:当一个协程获取锁后,其他需要访问共享资源的协程必须等待锁的释放。在高并发场景下,等待锁的时间可能会累计,导致整体任务执行时间变长。
- 上下文切换开销:每次获取和释放锁都会导致协程的上下文切换,这在一定程度上增加了系统的开销。
应对策略
- 减少锁的粒度:尽量缩小锁保护的代码块范围,只对真正需要保护的共享资源操作部分加锁,而不是对整个函数或较大的代码段加锁。例如,如果共享字典中有多个独立的键值对,并且不同的协程操作不同的键值对,可以考虑为每个键值对或相关的子集使用独立的锁。
- 使用读写锁:如果共享资源的读操作远多于写操作,可以使用读写锁(如
asyncio.Lock
可以模拟简单读写锁,或者使用更高级的库实现)。读操作可以同时进行,只有写操作需要独占锁,这样可以提高并发性能。 - 优化算法和数据结构:尽量使用线程安全的数据结构,如
asyncio.Queue
,在一些情况下可以避免使用锁。同时,优化算法,减少对共享资源的访问频率。 - 分布式处理:对于极高并发场景,可以考虑将任务分布到多个服务器上处理,避免单个进程内高并发带来的锁竞争问题。