Обновление: отредактирован заголовок, чтобы сосредоточиться на основной проблеме. Полное обновление смотрите в моем ответе.
В следующем коде a()
и b()
идентичны. Каждый из них считает от 0 до 9 одновременно, получая и выдавая блокировку каждые 2 счета.
import asyncio
lock = asyncio.Lock()
def a ():
yield from lock.acquire()
for i in range(10):
print('a: ' + str(i))
if i % 2 == 0:
lock.release()
yield from lock.acquire()
lock.release()
def b ():
yield from lock.acquire()
for i in range(10):
print('b: ' + str(i))
if i % 2 == 0:
lock.release()
yield from lock.acquire()
lock.release()
asyncio.get_event_loop().run_until_complete(asyncio.gather(a(), b()))
print('done')
Я ожидал чередующийся вывод, но вместо этого я получаю:
b: 0
b: 1
b: 2
b: 3
b: 4
b: 5
b: 6
b: 7
b: 8
b: 9
a: 0
a: 1
a: 2
a: 3
a: 4
a: 5
a: 6
a: 7
a: 8
a: 9
done
Кажется, что второй yield
на самом деле не уступает, а вместо этого немедленно повторно захватывает блокировку и продолжает.
Мне кажется, что это ошибка. Я прав? или есть другое объяснение?
Следующий код, модифицированный дополнительным начальным "noop" yield
, работает нормально, как и ожидалось. Это заставляет меня поверить, что блокировка действительно справедлива и, вероятно, правильна.
import asyncio
lock = asyncio.Lock()
def a ():
yield from lock.acquire()
yield from asyncio.sleep(0)
for i in range(10):
print('a: ' + str(i))
if i % 2 == 0:
lock.release()
yield from lock.acquire()
lock.release()
def b ():
yield from lock.acquire()
yield from asyncio.sleep(0)
for i in range(10):
print('b: ' + str(i))
if i % 2 == 0:
lock.release()
yield from lock.acquire()
lock.release()
asyncio.get_event_loop().run_until_complete(asyncio.gather(a(), b()))
print('done')
Выход:
a: 0
b: 0
a: 1
a: 2
b: 1
b: 2
a: 3
a: 4
b: 3
b: 4
a: 5
a: 6
b: 5
b: 6
a: 7
a: 8
b: 7
b: 8
a: 9
b: 9
done
Обратите внимание, что я выполняю выход без операции только один раз в начале, а не каждые 2 счета. Тем не менее, это приводит к тому, что чередование каждые 2 считается, как и ожидалось в первом фрагменте кода.
Просто есть некоторая оптимизация (на мой взгляд, ошибка) в планировщике, которая на самом деле не yield
при получении блокировки, которую никто не ждет.
Как еще объяснить первый вывод?
yield
: github.com /python/cpython/blob/master/Lib/asyncio/locks.py#L171 - person user2297550   schedule 18.01.2017threading.Lock()
CPython не освобождает GIL, если он может немедленно получить блокировку). Смыслasyncio.Lock
в том, чтобы гарантировать, что если вы вернетесь к циклу обработки событий, удерживая блокировку, никто другой не сможет получить блокировку, пока вы чего-то ожидаете. Если бы вы действительно делали что-то параллельное в своей сопрограмме, блокировка имела бы значение, но вы не имеете значения (ничего не ожидается, пока блокировка удерживается), поэтому блокировка не имеет значения. - person ShadowRanger   schedule 12.06.2019asyncio
; если вы читаете первые три абзаца документации модуля, в них упоминается утилита для ввода-вывода и задач, связанных с вводом-выводом. Весь смысл фреймворка состоит в том, чтобы выполнять функции ЦП, а затем, когда обычный код блокируется (при вводе-выводе, синхронизации и т. д.), он вместо этого совместно передает ЦП чему-то другому, готовому к работе. Но если блокировка не требуется, он всегда будет делать все возможное без передачи... - person ShadowRanger   schedule 14.06.2019if not lock.locked(): yield from asyncio.sleep(0)
непосредственно передyield from lock.acquire()
, чтобы убедиться, что вы так или иначе передаете управление), но они не собираются делать выбор дизайна, который по умолчанию замедляет всех без какой-либо пользы для производительности или правильности. - person ShadowRanger   schedule 14.06.2019asyncio
была официально добавлена в качестве базовой библиотеки Python,asyncio.Lock
породила метод.locked()
для тестирования именно этого. - person ShadowRanger   schedule 14.06.2019