Сравнение скорости Sync, Async и Threads в Python на примере запросов к сайту

Рейтинг: 0Ответов: 0Опубликовано: 07.08.2023

Тут решил на практике посмотреть разницу между Async и Threads, но либо я накодил что-то не то, либо у меня безграмотность в этом вопросе. Тестирую я это на http запросах к сайту.

import time
import aiohttp
import asyncio
import requests
import threading
from pprint import pprint


SLEEP_TIME = 2
URL = 'http://httpbin.org/get'
REQUESTS_COUNT = 20


class RequestLoop:

    def main(self):
        raise NotImplementedError


class AsyncRequestsLoop(RequestLoop):

    async def request(self, i: int):
        print(f'{i} started')
        async with aiohttp.ClientSession() as session:
            async with session.get(URL) as resp:
                print(f'{i} finished ({resp.status})')

    async def delay(self):
        await asyncio.sleep(SLEEP_TIME)

    async def main(self):
        await asyncio.gather(
            *(self.request(i) for i in range(REQUESTS_COUNT)),
            self.delay(),
        )


class SyncRequestsLoop(RequestLoop):

    def request(self, i: int):
        print(f'{i} started')
        resp = requests.get(URL)
        print(f'{i} finished ({resp.status_code})')

    def delay(self):
        time.sleep(SLEEP_TIME)

    def main(self):
        for i in range(REQUESTS_COUNT):
            self.request(i)
        self.delay()


class ThreadRequestsLoop(RequestLoop):

    def request(self, i: int):
        print(f'{i} started')
        resp = requests.get(URL)
        print(f'{i} finished ({resp.status_code})')

    def delay(self):
        time.sleep(SLEEP_TIME)

    def main(self):
        threads = []
        for i in range(REQUESTS_COUNT):
            thread = threading.Thread(target=self.request, args=(i,))
            threads.append(thread)
            thread.start()
        thread = threading.Thread(target=self.delay)
        threads.append(thread)
        thread.start()
        for tread in threads:
            tread.join()


def run_tests():
    time_start = time.time()
    SyncRequestsLoop().main()
    yield 'sync', time.time() - time_start

    time_start = time.time()
    asyncio.run(AsyncRequestsLoop().main())
    yield 'async', time.time() - time_start

    time_start = time.time()
    ThreadRequestsLoop().main()
    yield 'theads', time.time() - time_start


if __name__ == '__main__':
    pprint(list(run_tests()))
0 started
0 finished (200)
1 started
1 finished (200)
2 started
2 finished (200)
3 started
3 finished (200)
4 started
4 finished (200)
5 started
5 finished (200)
6 started
6 finished (200)
7 started
7 finished (200)
8 started
8 finished (200)
9 started
9 finished (200)
10 started
10 finished (200)
11 started
11 finished (200)
12 started
12 finished (200)
13 started
13 finished (200)
14 started
14 finished (200)
15 started
15 finished (200)
16 started
16 finished (200)
17 started
17 finished (200)
18 started
18 finished (200)
19 started
19 finished (200)
0 started
1 started
2 started
3 started
4 started
5 started
6 started
7 started
8 started
9 started
10 started
11 started
12 started
13 started
14 started
15 started
16 started
17 started
18 started
19 started
6 finished (200)
4 finished (200)
3 finished (200)
5 finished (200)
15 finished (200)
13 finished (200)
9 finished (200)
11 finished (200)
7 finished (200)
16 finished (200)
17 finished (200)
19 finished (200)
18 finished (200)
10 finished (200)
1 finished (200)
2 finished (200)
0 finished (200)
8 finished (200)
14 finished (200)
12 finished (200)
0 started
1 started
3 started
4 started
5 started
6 started
2 started
7 started
9 started
10 started
8 started
11 started
12 started
14 started
16 started
18 started
15 started
19 started
17 started
13 started
6 finished (200)
4 finished (200)
1 finished (200)
0 finished (200)
3 finished (200)
2 finished (200)
7 finished (200)
10 finished (200)
5 finished (200)
12 finished (200)
8 finished (200)
9 finished (200)
14 finished (200)
11 finished (200)
15 finished (200)
16 finished (200)
13 finished (200)
19 finished (200)
17 finished (200)
18 finished (200)
[('sync', 11.990408658981323),
 ('async', 2.0112106800079346),
 ('theads', 2.019129991531372)]

Первый вопрос: Тест потоков и асинхронности занимают примерно одно и то же время даже при количестве запросов к сайту 100. Я ожидал, что потоки будут работать синхронно из-за GIL. Почему это не так?

Второй вопрос: Если запустить только асинхронный тест, то будет создано еще стабильно 10 потоков в системе, даже если запросов к сайту 100. Но если запроса 4 создается 4 потока. А в асинхронном коде вообще поток должен быть только один. Сморю через htop. При запуске лишь теста потоков, потоки вообще не создается в системе, ну или 1 всего, не зависимо от количества запросов. Тоже непонятно почему так происходит -_-

UPD 1

Нашел от части пояснение для вопроса "Почему запросы к сайту в потоках не блокируются" в этом ответе https://stackoverflow.com/questions/32256775/how-does-the-gil-in-python-affect-the-downloading-of-webpages-in-parallel

Я так понимаю, потоки не популярны из-за стоимости их содержания. Переключения стоят дороже чем в асинхронном коде. Буду благодарен за дополняющие комментарии.

UPD 2

Корректировка второго вопроса. При тестировании потоков - потоки действительно создаются. 1 главный поток + 20 запросов к сайту + 1 поток с time.sleep = 22 потока (Рис.2).

Тем не менее остается непонятным почему 21 асинхронная функция порождают именно 10 потоков (Рис.1). Почему вообще порождаются потоки в асинхронном коде?

введите сюда описание изображения

введите сюда описание изображения

Ответы

Ответов пока нет.