Страницы

Поиск по вопросам

среда, 29 января 2020 г.

Почему так медленно? Зачем тогда потоки?

#python


Есть обычная функция которую я пробовал запускать в потоках и без, в каждом по три раза:

import time


def foo(count, num):
    for n in range(count):
        pass
    print('done %s' % num)


start = time.time()
foo(100000000, 1)
foo(100000000, 2)
foo(100000000, 3)
print('Done at:', time.time() - start)


Время исполнения 6.2 сек, теперь запускаю ту же функцию в потоках:

import threading, time


def foo(count, num):
    for n in range(count):
        pass
    print('done %s' % num)


start = time.time()
threads = []
for n in range(3):
    t = threading.Thread(target=foo, args=(100000000, n))
    t.start()
    threads.append(t)


for thread in threads:
    thread.join()

print('Done at:', time.time() - start)


Время исполнения 7 сек., теперь запускаю с помощью _thread:

import time, _thread as thread


def foo(count, num):
    for n in range(count):
        pass
    print('done %s' % num)
    blocks[num] = True


start = time.time()
blocks = [False for n in range(3)]

for i in range(3):
    thread.start_new_thread(foo, (100000000, i))

while not all(blocks):
    pass

print('Done at:', time.time() - start)


Здесь вообще почти 10 сек. Почему так ? зачем нужны тогда вообще потоки ели есть
эта глобальная блокировка интерпретатора?
    


Ответы

Ответ 1



Почему так медленно? Только один поток может исполнять Питон-код в любое время из-за GIL (CPython). Поэтому использование нескольких потоков не ускоряет код, подобный приведённому в вопросе, в Питоне. Пример с _thread у вас ещё медленнее из-за while True цикла в основном потоке. Зачем тогда потоки? Потоки могут быть использованы не только для улучшения производительности, но для реализации асинхронных вызовов, к примеру, чтобы избежать эффект "подвисания" GUI при выполнении блокирующих вызовов (таких как чтение вывода внешних команд): Мультизадачность на Python: выполнить две долгие функции одновременно, не блокируя GUI. GIL отпускается во время блокирующих системных вызовов. В частности, тех которые ввод/вывод реализуют — к примеру, это позволяет скачивать несколько веб-страниц одновременно в Питоне. A very simple multithreading parallel URL fetching (without queue). В тех случаях когда производительность важна, в реальных задачах такие циклы реализованы в C расширениях типа numpy, regex, lxml, sqlite3, или написанных руками на Cython, которые могут отпустить GIL, что может улучшить производительность как на одном ядре на порядок/два, так и позволяет несколько CPU ядер задействовать одновременно. Вызов С функций через ctypes, также отпускает GIL. Реализации Питона, в которых нет GIL существуют уже давно (Jython, IronPython). Очевидно, что они не вытеснили реализации, использующие GIL, такие как CPython, Pypy. Стоит явно упомянуть, что разные Питон-процессы, в частности, такие как созданные с помощью multiprocessing модуля, не разделяют GIL (у каждого процесса свой GIL). Несколько Питон процессов могут все ядра использовать. Модель параллельности (concurrency), реализованная на процессах поощряет разделение данных и использование коммуникации для общения/синхронизации между разными ветками одновременных вычислений, не используя общую память (хотя можно память разделять в некоторых случаях, если необходимо). Это можно суммировать слоганом из Go языка, на который оказала влияние CSP теория Хоара (1978): Do not communicate by sharing memory; instead, share memory by communicating.

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

Отправить комментарий