繁体   English   中英

我可以在异步协程上使用阻塞锁吗?

[英]Can I use an blocking lock on an asynchronous coroutine?

我试图了解在异步上下文中使用同步锁有什么问题。 在我的代码中,我已经达到了多个协程可能访问同一个共享资源的地步。 获取此资源的 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 管理器应该能够始终返回一个令牌,无论是从 memory、存储或通过从第三方 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()

令人惊讶的是,我没有找到关于这个主题的太多信息,这可能指出我做错了什么,但即使我做错了,我仍然想知道 go 可能出错的事情是什么。

据我了解,阻塞锁(即threading.Lock )持有用于运行协程的线程,这意味着可以使用同一线程的其他协程将无法运行。 异步锁(即asyncio.Lock )将允许将线程分配给其他任务。

所以我的问题是:

会不会因为协程内部使用阻塞锁而出现其他并发问题?

其他不访问共享资源的协程是否会受到锁的影响(即它们是否可以被调度到其他线程上)?

会不会因为协程内部使用阻塞锁而出现其他并发问题?

这取决于run_async的作用。 据我了解你的问题,阻塞锁不是“在协程内”,它在一个普通的 function 内,它本身是通过run_async实用程序调用的。 由于 function 在单独的线程中运行,因此threading.Lock是保护共享资源的自然方式。

您找不到太多关于此的信息,因为您所做的并不是 asyncio 的设计用途。 如果您的代码包含阻塞调用,您可能应该使用线程而不是 asyncio。 asyncio 的全部意义在于在底层使用异步调用,并使用await在单个线程内实现并发,从而让其他线程执行。 如果使用得当,asyncio 可以提高可伸缩性并消除因不受控制的线程切换而导致的竞争条件。 在您的代码中,您不会获得这些好处,因为您最终只是使用了线程池,并为同步付出了代价。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM