Ожидание вызова, вызывающее неожиданное поведение атрибута экземпляра - PullRequest
0 голосов
/ 05 декабря 2018

У меня возникли проблемы с выяснением того, почему раскомментирование await asyncio.sleep(1) приводит к печати Test 10 раз.Кажется, что инициализация атрибута val не выполняется при использовании асинхронного.

Не следует соблюдать инициализацию и печатать только один раз, поскольку это один и тот же экземпляр.Как можно устранить это поведение, когда awaitable звонки присутствуют?

class TestAsync:

    def __init__(self):
        self.val = None

    async def some_fun(self):
        if not self.val:
            # await asyncio.sleep(1)  # Magic line
            print('Test')
            self.val = 10

async def main(loop):
    a = TestAsync()
    tasks = [a.some_fun() for _ in range(10)]
    return await asyncio.gather(*tasks)

if __name__ == '__main__':
    cur_loop = asyncio.get_event_loop()
    cur_loop.run_until_complete(main(cur_loop))

Ответы [ 2 ]

0 голосов
/ 05 декабря 2018

Это совершенно не та же проблема, что и в потоках.

Все сопрограммы работают в одном потоке, в сопрограммах нет проблем с многопоточностью.

Но проблема заключается впереключатель сопрограмм.Когда вы используете await asyncio.sleep(1), это ожидание вызовет переключение контекста с одной сопрограммы на другую.

Давайте возьмем две сопрограммы в качестве примера: C1 и C2.Итак, сначала есть очередь выполнения для этих двух сопрограмм: Q{C1, C2}.Затем C1 запускается для выполнения, и без await переключателя нет, поэтому C1 будет выполнен полностью.Затем для выполнения запускается C2.

Таким образом, порядок выполнения - C1 -> C2.Это абсолютно линейно.

Но когда есть await, это вызовет переключение.Это означает, что C1 будет остановлен и вставлен в конец очереди.Тогда C2 будет вытолкнут для выполнения.При этом C2 также будет остановлен на этой строке и вставлен в конец очереди.Далее C1 будет снова выведено и выполнено полностью.

Таким образом, порядок выполнения будет C1(before await) -> C2(before await) -> C1(the rest) -> C2(the rest).

Очевидно, что все ваши сопрограммы будут остановлены после проверки val.Что является основной проблемой.Это не проблема гонок параллелизма, но проблема реального понимания того, как await повлияет на вашу программу.

0 голосов
/ 05 декабря 2018

asyncio не позволяет вам перестать думать о проблемах параллелизма.Вы сталкиваетесь почти с той же самой проблемой, с которой вы столкнулись бы с потоками.

Каждая сопрограмма some_fun, которая видит ложное значение self.val, продолжается в теле оператора if.Сколько сопрограмм увидит такое значение, зависит от того, сколько сопрограмм пройдет тест if, прежде чем один из них установит self.val в 10.

Без sleep, первый сопрограмма установит self.valнемедленно 10, не давая никому другого шанса вмешаться.С sleep каждая сопрограмма переходит в режим сна и позволяет другим бежать, и все они видят None до того, как любой из них изменит значение.

...