Почему этот код, использующий цикл asyncio for, работает не быстрее, чем синхронный код?

Я узнаю об asyncio Python из книги О'Рейли «Использование Asyncio в Python».

В этой книге есть пример асинхронного цикла for:

# Example 3-26. Easier with an async generator
import asyncio


# Mock Redis interface
class Redis:
    async def get(self, key):
        await asyncio.sleep(1)
        return 'value'


# Mock create_redis
# Real one: aioredis.create_redis
async def create_redis(socket):
    await asyncio.sleep(1)
    return Redis()


async def do_something_with(value):
    await asyncio.sleep(1)


# Our function is now declared with async def , making it a coroutine
# function, and since this function also contains the yield keyword, we refer
# to it as an asynchronous generator function.
async def one_at_a_time(redis, keys):
    for k in keys:
        # We don’t have to do the convoluted things necessary in the previous
        # example with self.ikeys: here, we just loop over the keys directly
        # and obtain the value...
        value = await redis.get(k)
        # ...and then yield it to the caller, just like a normal generator.
        yield value


# The main() function is identical to the version in Example 3-25.
async def main():
    redis = await create_redis(('localhost', 6379))
    keys = ['Americas', 'Africa', 'Europe', 'Asia']
    async for value in one_at_a_time(redis, keys):
        await do_something_with(value)

start = time.time()
asyncio.run(main())
end = time.time()
print(end - start)

#print result is 9.012349128723145

Но я думаю, что он не запускается асинхронно. Это мой код синхронизации для сравнения с приведенным выше кодом:

class Redis:
    def get(self, key):
        time.sleep(1)
        return 'value'

def create_redis(socket):
    time.sleep(1)
    return Redis()


def do_something_with(value):
    time.sleep(1)


def one_at_a_time(redis, keys):
    for k in keys:
        value = redis.get(k)
        yield value


# The main() function is identical to the version in Example 3-25.
def main():
    redis = create_redis(('localhost', 6379))
    keys = ['Americas', 'Africa', 'Europe', 'Asia']
    tasks = []
    for value in one_at_a_time(redis, keys):
        do_something_with(value)

start = time.time()
main()
end = time.time()
print(end-start)

# print result 9.025717973709106

Время выполнения кода такое же.

Является ли первый код асинхронным кодом? Я думаю, что это не так, и я не могу понять, почему в книге написано, что это асинхронный код.

Ваш код не обязательно будет работать быстрее только потому, что вы используете asyncio. Проверьте этот пост, чтобы узнать, поможет ли он: stackoverflow.com/questions/1050222/…

Rafael Marques 08.06.2024 19:24

ваш код выполняет только одну задачу redis.get(), и он не может работать быстрее, потому что ему приходится ждать завершения этой задачи, чтобы запустить следующую redis.get(). Возможно, если вы запустите его create_task() с помощью redis.get(), то он запустит множество задач одновременно. См. примеры в документации: Сопрограммы и задачи

furas 08.06.2024 21:47
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
2
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваш код выполняет одну задачу redis.get(k) и ожидает ее завершения, чтобы выполнить следующую redis.get(k), поэтому он не может работать быстрее.

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

Этот код создает все задачи (без использования await) и использует gather() для их одновременного запуска.

async def one_at_a_time(redis, keys):
    tasks = []
    for k in keys:
        tasks.append(redis.get(k))

    results = await asyncio.gather(*tasks)

    for item in results:
        yield item

И это занимает всего 6,0 секунды.

Но он все равно может работать быстрее, потому что на это уходит results 1 секунда вместо 4 секунд, но тратится время на запуск do_something_with() один за другим, а не на их одновременное выполнение.


Полный рабочий код:

import asyncio
import time
import random 


class Redis:
    async def get(self, key):
        print('get')
        await asyncio.sleep(1)
        return random.randint(0, 10)


async def create_redis(socket):
    await asyncio.sleep(1)
    return Redis()


async def do_something_with(value):
    print('do something with', value)
    await asyncio.sleep(1)


async def one_at_a_time(redis, keys):
    tasks = []
    for k in keys:
        tasks.append(redis.get(k))

    results = await asyncio.gather(*tasks)

    print('results:', results) 
    for item in results:
        yield item


async def main():
    redis = await create_redis(('localhost', 6379))
    keys = ['Americas', 'Africa', 'Europe', 'Asia']
    async for value in one_at_a_time(redis, keys):
        await do_something_with(value)


start = time.time()
asyncio.run(main())
end = time.time()
print('time:', end - start)

#print result is 6.0089991092681885

А вот код, который использует тот же метод, что и gather()
чтобы запустить все do_something_with() одновременно.

И это дает время 3,0 секунды.

import asyncio
import time
import random 


class Redis:
    async def get(self, key):
        print('get')
        await asyncio.sleep(1)
        return random.randint(0, 10)


async def create_redis(socket):
    await asyncio.sleep(1)
    return Redis()


async def do_something_with(value):
    print('do something with', value)
    await asyncio.sleep(1)


async def one_at_a_time(redis, keys):
    tasks = []
    for k in keys:
        tasks.append(redis.get(k))

    results = await asyncio.gather(*tasks)

    print('results:', results) 
    for item in results:
        yield item


async def main():
    redis = await create_redis(('localhost', 6379))
    keys = ['Americas', 'Africa', 'Europe', 'Asia']

    tasks = []
    async for value in one_at_a_time(redis, keys):
        tasks.append(do_something_with(value))
    await asyncio.gather(*tasks)


print('start')    
start = time.time()
asyncio.run(main())
end = time.time()
print('time:', end - start)

#print result is 3.0061380863189697
#print result is 6.0089991092681885 
#print result is 9.012349128723145

Более простой пример, показывающий разницу между

  • await function()...
  • x = function()...await x
  • x = create_task(function())...await x
import asyncio
import time

async def my_function(text):
    print(text, 'my_function 1')
    await asyncio.sleep(0.5)
    print(text, 'my_function 2')
    await asyncio.sleep(0.5)
    print(text, 'my_function 3')
    await asyncio.sleep(0.5)        
    print(text, 'my_function 4')
    await asyncio.sleep(0.5)        
    
async def example1():
    # create, start, and wait for result (blocking)
    await my_function('A')
    await my_function('B')
    
    await asyncio.sleep(1)
    print('something else 1')
    await asyncio.sleep(1)
    print('something else 2')
    await asyncio.sleep(1)
    print('something else 3')
    await asyncio.sleep(1)
    print('something else 4')
    await asyncio.sleep(1)

async def example2():
    # create (not blocking)
    a = my_function('A')
    b = my_function('B')
    
    await asyncio.sleep(1)
    print('something else 1')
    await asyncio.sleep(1)
    print('something else 2')
    await asyncio.sleep(1)
    print('something else 3')
    await asyncio.sleep(1)
    print('something else 4')
    await asyncio.sleep(1)

    # start, and wait for result (blocking)
    await a
    await b
    
async def example3():

    # create and start (not blocking)
    a = asyncio.create_task(my_function('A'))
    b = asyncio.create_task(my_function('B'))

    await asyncio.sleep(1)
    print('something else 1')
    await asyncio.sleep(1)
    print('something else 2')
    await asyncio.sleep(1)
    print('something else 3')
    await asyncio.sleep(1)
    print('something else 4')
    await asyncio.sleep(1)

    # wait for result (blocking)
    await a
    await b
    # OR
    #await asyncio.gather(a, b)

async def main():
    print('--- example 1 ---')
    start = time.time()    
    await example1()
    end = time.time()
    print('time:', (end - start))
    
    print('--- example 2 ---')
    start = time.time()    
    await example2()
    end = time.time()
    print('time:', (end - start))
    
    print('--- example 3 ---')
    start = time.time()    
    await example3()
    end = time.time()
    print('time:', (end - start))
    
    
asyncio.run(main())

Спасибо за ответ, я ждал этого ответа

jihyeon 08.07.2024 10:32

Другие вопросы по теме