Python: Как не получить взаимоблокировку для этой простой функциональности?

#python #python-asyncio

#python #python-asyncio

Вопрос:

Я реализовал простой класс с именем Saver, который имеет две функции, которые могут выполнять его вызывающие. Это save_all() и save_one()

Как следует из названия, когда я выполняю save_all() , я хочу, чтобы он выполнял функцию save_one() в цикле. Я просто хочу убедиться, что только один вызывающий может выполнить save_all() или save_one() в данный момент времени извне.

 
import asyncio

class Saver:
  def __init__(self):
    # it's lock
    self.lock = asyncio.Lock()


  def save_all():
     yield from self.lock.acquire()
     for i in range(3):
        save_one(i)
     self.lock.release()

  def save_one():
     yield from self.lock.acquire()
     print(i)
     self.lock.release()

  

Теперь происходит то, что если я вызываю save_all() , он получает блокировку, а оператор save_one() print() никогда не выполняется.

Как мне убедиться, что только вызывающий может вызывать либо save_all(), либо save_one() извне:

 saver = Saver()
saver.save_all()
  

Комментарии:

1. Либо координируйте с отдельным флагом, который save_one не должен пытаться получить другую блокировку, либо используйте отдельные блокировки для save_one and save_all .

2. save_all Действительно ли нужна блокировка? Кроме того, вы, вероятно, захотите использовать threading.Lock , если вы не используете блокировки внутри сопрограмм asyncio.

3. @dirn threading.Lock — неправильный инструмент, потому что он заблокирует цикл событий во время ожидания получения блокировки (возможно, вызывая взаимоблокировку). Похоже, что OP использует блокировку в коде asyncio, по-видимому, используя yield from синтаксис.

4. @user4815162342 но, похоже, OP не использует coroutine декоратор.

5. @dirn Это хороший момент, но, с другой стороны, OP опускает self список параметров, но использует его в телах функций, вызывает методы, как если бы они были функциями верхнего уровня, и так далее. Показанный код, по-видимому, является лишь приближением к фактическому коду OP, который, как я ожидаю, будет включать декоратор, потому что он скопирован из устаревшего руководства.

Ответ №1:

Ввод печати save_one() не выполняется в коде, как показано, потому что код никогда не ожидает save_one() , он просто вызывает его. (Но у кода есть и другие тривиальные недостатки, поэтому, возможно, он больше похож на псевдокод.) Также обратите внимание, что сопрограммы yield-from устарели, поэтому вы должны использовать async def and await вместо def and yield from при написании async Python.

Поскольку asyncio не поставляется с реентерабельной блокировкой, самый простой способ устранить проблему — заблокировать только точки входа API. Базовые методы реализации могут полностью избежать блокировки, поскольку об этом заботятся на границах системы. Тогда взаимоблокировки не будет, потому что internal _save_all вызовет internal _save_one , и блокировка будет получена только один раз (in save_call , перед вызовом _save_all , или in save_one , перед вызовом _save_one , в зависимости от обстоятельств):

 class Saver:
    def __init__(self):
        self.lock = asyncio.Lock()

    async def save_all(self):
        async with self.lock:
            await self._save_all()

    async def save_one(self):
        async with self.lock:
            await self._save_one()

    async def _save_all(self):
        # private method, must be invoked with the lock held
        for i in range(3):
            await self._save_one(i)

    async def _save_one(self, i):
        # private method, must be invoked with the lock held
        print(i)