MST

星途 面试题库

面试题:Go中条件变量与互斥锁协同处理资源竞争及高效唤醒问题

假设有多个协程竞争访问共享资源,共享资源有一定的限制数量。当共享资源不足时,协程需要等待,一旦有资源释放,等待的协程能够被高效唤醒并获取资源。请使用条件变量和互斥锁设计并实现这个场景,说明如何避免虚假唤醒以及如何优化唤醒效率。
14.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
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来检查资源是否可用。这是因为在某些情况下,条件变量可能会被虚假唤醒(例如在没有调用notifynotify_all的情况下)。使用while循环可以确保只有在资源确实可用时才会继续执行,从而避免虚假唤醒带来的问题。如上述代码中:

while self.current_count <= 0:
    self.cond.wait()

优化唤醒效率

  1. 使用notifynotify_all的选择
    • 如果只有一个等待的协程需要获取资源,使用notify方法可以更高效地唤醒单个协程,避免不必要地唤醒所有等待的协程。在上述代码中,release方法里调用self.cond.notify()只唤醒一个等待的协程。
    • 如果多个等待的协程都需要获取资源(例如资源数量增加后多个协程都能获取),则使用notify_all方法。但这种情况下可能会导致不必要的上下文切换,所以要根据实际场景选择。
  2. 减少锁的持有时间:尽量缩短在持有锁的情况下执行的代码,例如在获取和释放资源时,只在必要的资源计数操作时持有锁,其他操作(如模拟使用资源的asyncio.sleep)在锁外部执行,这样可以减少其他协程等待锁的时间,提高整体效率。如上述代码中,模拟使用资源的操作在锁外部执行。