python - 我可以在异步协程上使用阻塞锁吗?
问题描述
我试图了解在异步上下文中使用同步锁有什么问题。在我的代码中,我已经达到了多个协程可能访问同一个共享资源的地步。获取此资源的 API 是同步的,我现在不想将其变为异步的。
这是我设计的API。
class OAuthManager(abc.ABC):
"""
Represents an OAuth token manager.
The manager main responsibility is to return an oauth access token
and manage it's lifecycle (i.e. refreshing and storage).
"""
@abc.abstractmethod
def get_access_token(self) -> OAuthToken:
"""
Should return the [OAuthToken] used to authorize the client
to access OAuth protected resources.
The implementation should handle refreshing and saving the token,
if necessary.
"""
pass
本质上,OAuth 管理器应该能够始终从内存、存储或通过从第三方 API 刷新它来返回一个令牌。
我通过将同步任务卸载到后台线程来进行异步调用,我想这不会是一个大问题,因为这些方法都是 IO 绑定的。
class AsyncEndpoint:
"""
Transforms an sync endpoint into an async one.
The asynchronous tasks are executed by offloading a synchronous task into
a executor thread. Due to the GIL, this method is only useful with IO
bound tasks.
"""
def __init__(self, endpoint):
self.endpoint = endpoint
def __getattr__(self, name: str) -> Union[Any, Callable]:
attr = getattr(self.endpoint, name)
if not callable(attr):
return attr
async def _wrapper(*args, **kwargs):
return await utils.run_async(attr, *args, **kwargs)
return _wrapper
异步运行的方法将使用同步get_access_token
方法,并且此访问令牌是协程之间的共享资源。
我正在尝试使用装饰器模式为此方法添加锁。
class LockOAuthManager(OAuthManager):
"""
Most of the times tokens will be retrieved from memory
which means the lock won't be locked for a long time,
In the worst case, the lock will remain locked for
an full http request period.
But if one thread needed to retrieve the token from the
API, the same would have to happen to the other threads,
so I believe the waiting penalty would happen anyway.
"""
def __init__(self, oauth_manager: OAuthManager):
self.lock = threading.Lock()
self.oauth_manager = oauth_manager
def get_access_token(self) -> OAuthToken:
with self.lock:
return self.oauth_manager.get_access_token()
令人惊讶的是,我没有找到关于这个主题的太多信息,这可能表明我做错了什么,但即使我做错了,我仍然想知道哪些事情可能会出错。
据我了解,阻塞锁(即threading.Lock
)持有用于运行协程的线程,这意味着可以使用同一线程的其他协程将无法运行。异步锁(即asyncio.Lock
)将允许将线程分配给其他任务。
所以我的问题是:
会不会因为协程内部使用阻塞锁而出现其他并发问题?
其他不访问共享资源的协程是否会受到锁的影响(即它们是否可以被调度到其他线程上)?
解决方案
会不会因为协程内部使用阻塞锁而出现其他并发问题?
这取决于做什么run_async
。据我了解您的问题,阻塞锁不是“在协程内部”,而是在一个普通函数内部,该函数本身是通过run_async
实用程序调用的。由于该函数在单独的线程中运行,因此 athreading.Lock
是保护共享资源的自然方式。
您找不到太多关于此的信息,因为您所做的并不是 asyncio 的设计用途。如果您的代码包含阻塞调用,您可能应该使用线程而不是 asyncio。asyncio 的全部意义在于在后台使用异步调用,并在单个线程内实现并发,await
从而让其他线程执行。如果使用得当,asyncio 可以提高可伸缩性并消除因不受控制的线程切换而导致的竞争条件。在您的代码中,您不会获得这些好处,因为您最终只是使用了线程池,并为同步付出了代价。
推荐阅读
- angular - Angular 7 路由路径打开源图
- java - 来自 ManyToOne 惰性关系的实体未加载到不同的类中
- c# - 在 ASP.NET Core 3.1 中,如何在未来的特定日期和时间安排具有托管服务的后台任务(Cron 作业)?
- firebase - 无效的`Podfile`文件:无效的`RNFBDatabase.podspec`文件:没有这样的文件或目录@ rb_sysopen - ../app/package.json - 反应原生
- mysql - 根据引用同一表的视图从表中删除行
- python - 如何在odoo中编写原始sql查询
- scala - Scala Spark 根据另一列中值的聚合计数在数据框中创建一个新列
- sql - Azure 数据工厂中的复制数据活动后更新
- java - 如何使用 Spring 集成流处理异常
- javascript - 使 Django 表单交互