Что такое потоки? Зачем вам это нужно?

Python - линейный язык. Однако модуль многопоточности пригодится, если вам нужно немного больше вычислительной мощности.

Многопоточность в Python не может использоваться для параллельных вычислений ЦП. Но он идеально подходит для операций ввода-вывода, таких как очистка веб-страниц, потому что процессор простаивает в ожидании данных.

Использование потоков меняет правила игры, поскольку многие сценарии, связанные с вводом-выводом сети / данных, проводят большую часть своего времени в ожидании данных из удаленного источника.

Поскольку загрузки могут не быть связаны (например, если вы просматриваете отдельные веб-сайты), процессор может загружать данные из разных источников параллельно и комбинировать результат в конце.

Для процессов, интенсивно использующих ЦП, использование модуля многопоточности малоэффективно.

Threading входит в стандартную библиотеку:

import threading from queue
import Queue
import time

Вы можете использовать target в качестве вызываемого объекта, args для передачи параметров функции и start для запуска потока.

def testThread(num):
    print num

if __name__ == '__main__':
    for i in range(5):
        t = threading.Thread(target=testThread, arg=(i,))
        t.start()

Если вы никогда раньше не видели if __name__ == '__main__':, это, по сути, способ убедиться, что код, вложенный в него, будет выполняться только в том случае, если сценарий запускается напрямую (не импортируется).

Замок

Часто вам нужно, чтобы ваши потоки могли использовать или изменять переменные, общие для потоков. Для этого вам нужно использовать что-то, известное как lock.

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

Представьте две функции, каждая из которых выполняет итерацию переменной на 1. Блокировка позволяет гарантировать, что одна функция может получить доступ к переменной, выполнить вычисления и записать обратно в эту переменную, прежде чем другая функция сможет получить доступ к той же переменной.

Вы можете использовать блокировку печати, чтобы гарантировать, что только один поток может печатать одновременно. Это предотвращает перемешивание текста (и повреждение данных) при печати.

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

print_lock = threading.Lock()

def threadTest():
    # when this exits, the print_lock is released
    with print_lock:
        print(worker)

def threader():
  while True:
    # get the job from the front of the queue
    threadTest(q.get())
    q.task_done()

q = Queue()
for x in range(5):
    thread = threading.Thread(target = threader)
    # this ensures the thread will die when the main thread dies
    # can set t.daemon to False if you want it to keep running
    t.daemon = True
    t.start()

for job in range(10):
    q.put(job)

Многопоточность - не всегда идеальное решение

Я обнаружил, что многие гиды склонны игнорировать отрицательные стороны использования инструмента, которому они только что пытались научить вас. Важно понимать, что у использования всех этих инструментов есть как плюсы, так и минусы. Например:

  1. Управление потоками связано с накладными расходами, поэтому вы не хотите использовать его для основных задач (как в примере).
  2. Многопоточность увеличивает сложность программы, что может затруднить отладку.

Что такое многопроцессорность? Чем это отличается от многопоточности?

Без многопроцессорности программы Python не могут максимально использовать спецификации вашей системы из-за GIL (Global Interpreter Lock). Python не был разработан с учетом того, что персональные компьютеры могут иметь более одного ядра (что показывает, сколько лет языку).

GIL необходим, потому что Python не является потокобезопасным и существует глобальная блокировка при доступе к объекту Python. Хотя и не идеальный, это довольно эффективный механизм управления памятью. Что мы можем сделать?

Многопроцессорность позволяет создавать программы, которые могут выполняться одновременно (в обход GIL) и использовать все ядро ​​вашего процессора. Хотя он принципиально отличается от библиотеки потоковой передачи, синтаксис очень похож. Библиотека многопроцессорной обработки предоставляет каждому процессу свой собственный интерпретатор Python и каждому свой собственный GIL.

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

Давайте начнем

import multiprocessing
def spawn():
  print('test!')

if __name__ == '__main__':
  for i in range(5):
    p = multiprocessing.Process(target=spawn)
    p.start()

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

for i in range(5):
  p = multiprocessing.Process(target=spawn)
  p.start()
  p.join() # this line allows you to wait for processes

Если вы хотите передать аргументы вашему процессу, вы можете сделать это с помощью args:

import multiprocessing
def spawn(num):
  print(num)

if __name__ == '__main__':
  for i in range(25):
    ## right here
    p = multiprocessing.Process(target=spawn, args=(i,))
    p.start()

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

Недостатки

Как и в случае с многопроцессорностью, у многопроцессорности все еще есть недостатки ... вам нужно выбрать свой яд:

  1. Из-за перетасовки данных между процессами возникают накладные расходы ввода-вывода.
  2. Вся память копируется в каждый подпроцесс, что может привести к значительным накладным расходам для более важных программ.

Заключение

Когда следует использовать многопоточность или многопроцессорность?

  • Если в вашем коде много операций ввода-вывода или использования сети, многопоточность - ваш лучший выбор из-за ее низких накладных расходов.
  • Если у вас есть графический интерфейс, используйте многопоточность, чтобы поток пользовательского интерфейса не блокировался.
  • Если ваш код привязан к процессору, вы должны использовать многопроцессорность (если на вашем компьютере несколько ядер)

Просто оговорка: мы здесь, @ Timber, лесозаготовительная компания. Нам бы очень понравилось, если бы вы попробовали наш продукт (он действительно великолепен!), Но это все, что мы собираемся рекламировать.

Если вы хотите получать больше сообщений от Timber в свой почтовый ящик, не стесняйтесь подписаться здесь. Мы обещаем, что не будет спама, только отличный контент еженедельно.

Изначально опубликовано на timber.io.