面试题答案
一键面试import asyncio
import threading
class ResourceManager:
def __init__(self, max_count):
self.max_count = max_count
self.current_count = max_count
self.lock = threading.Lock()
self.cond = threading.Condition(self.lock)
async def acquire(self):
with self.lock:
while self.current_count <= 0:
self.cond.wait()
self.current_count -= 1
async def release(self):
with self.lock:
self.current_count += 1
self.cond.notify()
async def worker(resource_manager, worker_id):
print(f"Worker {worker_id} is trying to acquire resource")
await resource_manager.acquire()
print(f"Worker {worker_id} acquired resource")
await asyncio.sleep(1) # 模拟使用资源
print(f"Worker {worker_id} is releasing resource")
await resource_manager.release()
async def main():
resource_manager = ResourceManager(2)
tasks = [worker(resource_manager, i) for i in range(5)]
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main())
避免虚假唤醒
在获取共享资源时,使用while
循环而不是if
来检查资源是否可用。这是因为在某些情况下,条件变量可能会被虚假唤醒(例如在没有调用notify
或notify_all
的情况下)。使用while
循环可以确保只有在资源确实可用时才会继续执行,从而避免虚假唤醒带来的问题。如上述代码中:
while self.current_count <= 0:
self.cond.wait()
优化唤醒效率
- 使用
notify
和notify_all
的选择:- 如果只有一个等待的协程需要获取资源,使用
notify
方法可以更高效地唤醒单个协程,避免不必要地唤醒所有等待的协程。在上述代码中,release
方法里调用self.cond.notify()
只唤醒一个等待的协程。 - 如果多个等待的协程都需要获取资源(例如资源数量增加后多个协程都能获取),则使用
notify_all
方法。但这种情况下可能会导致不必要的上下文切换,所以要根据实际场景选择。
- 如果只有一个等待的协程需要获取资源,使用
- 减少锁的持有时间:尽量缩短在持有锁的情况下执行的代码,例如在获取和释放资源时,只在必要的资源计数操作时持有锁,其他操作(如模拟使用资源的
asyncio.sleep
)在锁外部执行,这样可以减少其他协程等待锁的时间,提高整体效率。如上述代码中,模拟使用资源的操作在锁外部执行。