Понимание многопоточности в Python для начинающих

Если вы начали изучать кодирование или использование инструментов программирования, вы, возможно, слышали такие термины, как «многопроцессорность» или «многопоточность», если вы еще не изучили эти вещи, то это поможет вам понять эти замечательные концепции программирования простым способом. В этой статье мы обсудим только многопоточность.

Итак, давайте начнем это захватывающее учебное путешествие.

Многопоточность впервые появилась в 1950-х годах, а одновременная многопоточность (SMT) была исследована IBM в 1968 году. До этого все ЦП могли создавать только один поток для каждого процесса.

Большинство попыток истории многопоточных процессоров начинаются с PPU в CDC 6600. Однако идея совместного использования одного пути выполнения среди нескольких потоков выполнения возникла по крайней мере десятилетием ранее.

Несколько самых начальных подходов к многопроцессорной обработке.

  • NBS SEAC (1950 г.) и DYSEAC (1954 г.) — два потока
  • Lincoln Labs TX-2 (конец 1950-х) — до 33 потоков

Что такое нить?

поток – это последовательность таких инструкций в программе, которая может выполняться независимо от другого кода. Для простоты можно предположить, что поток — это просто подмножество процесса!

Все потоки выполняются внутри одного процесса и совместно используют одно и то же пространство памяти друг с другом.

Поток содержит всю эту информацию в блоке управления потоком (TCB):

  • Идентификатор потока: уникальный идентификатор (TID) назначается каждому новому потоку.
  • Указатель стека: указывает на стек потока в процессе. Стек содержит локальные переменные в области видимости потока.
  • Счетчик программ: регистр, в котором хранится адрес инструкции, выполняемой в данный момент потоком.
  • Состояние потока: может быть запущено, готово, ожидает, запущено или выполнено.
  • Набор регистров потока: регистры, назначенные потоку для вычислений.
  • Указатель родительского процесса. Указатель на блок управления процессом (PCB) процесса, в котором находится поток.

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

Например, идентификатор потока (TID) можно использовать для остановки и получения дополнительной информации о запущенном потоке в случае блокировки потока из-за какой-либо ошибки.

Что такое многопоточность

Многопоточность определяется как способность процессора одновременно выполнять несколько потоков.

Нити можно отнести к одной из двух категорий:

  • Привязка ЦП: они интенсивно используют ЦП.
  • Привязка ввода-вывода: они часто блокируются из-за операции ввода-вывода, оставляя ЦП бездействующим.

Использование только многопоточности без многопоточности в случаях с привязкой к процессору не даст нам большого преимущества с точки зрения скорости, в этих случаях мы можем использовать многопоточность + многопоточность. Но мы не будем обсуждать это здесь.

Многопоточность лучше всего подходит для случаев, связанных с вводом-выводом.

Approach       Python package     Better when bound to:    Parallel?
Threading        threading              I/O                   No
Multiprocessing  multiprocessing        CPU                   Yes

Зачем использовать многопоточность?

Односложным ответом на это может быть "Для быстрого приложения". Могут быть сценарии, когда вы можете захотеть использовать многопроцессорность вместо многопоточности, но об этом в другой раз.

Давайте рассмотрим ситуацию для правильного понимания этого.

Мы хотим создать функцию (func()) для загрузки 50 изображений с веб-сайта. Отправляем запрос на серверОтвет получен через 4 секунды. Затем мы отправляем еще один запрос, он ждет 4 секунды, и мы продолжаем этот процесс для всех URL-адресов изображений, которые у нас есть.

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

В этом случае мы можем использовать многопоточность для порождения нескольких потоков, когда предыдущий ожидает ответа от сервера, делая это, мы можем сэкономить много времени, не просто ожидая ответа. Потоки Python также могут ожидать threading.Lock или других объектов синхронизации из модуля потоков.

Визуальное представление приведенного выше сценария.

Глобальная блокировка интерпретатора (GIL)

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

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

Это явление может привести к уязвимости системы безопасности, известной как состояние гонки.

Давайте уделим немного времени и разберем приведенную выше диаграмму. Как мы видим, есть factorialfunction и два потока 1 и 2, поток 1 находится в заблокированном состоянии, а поток 2 находится в состоянии ожидания. Это означает, что только один из потоков может получить доступ к функции.

Теперь предположим, что функция factorial выполняется за 2 секунды. Тогда в идеальном случае оба потока должны завершить выполнение за 2 секунды. Однако в Python это не так, оба потока будут работать последовательно, а не параллельно друг другу, поскольку в этом случае мы ограничены ЦП для выполнения ограниченного количества вычислений.

Следовательно, ГИЛ.

  • Ограничивает операцию нарезки
  • Параллельное выполнение ограничено
  • GIL гарантирует, что в интерпретаторе одновременно работает только один поток.
  • Это помогает упростить различные низкоуровневые детали, такие как управление памятью.

Создание брутфорсера каталогов

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

Подход

Существует несколько различных способов реализации многопоточности в Python: использование threadPoolExecutor, модуль многопоточности, использование реализации Queue() в модуле многопоточности и т. д.

Мы будем использовать модуль Threading для его реализации.

Прежде всего, нам нужно прочитать файл списка слов. Мы перенесем весь список слов в список Python. Теперь мы разделим этот список на более мелкие части списков в зависимости от количества потоков, которые мы хотим выполнить.

len(Original_list)=500
Num_Of_Threads=50
Chunk_Length=len(Original_list)// Num_Of_Threads
New_list= Chunks of lists of Chunk_Length

После создания фрагментов списков мы передадим этот список для выполнения запросов и проверим, являются ли переданные слова внутри списка фрагментов допустимым каталогом или нет.

Вот и весь код🔗

Анализ кода

Теперь давайте подробно обсудим, что делает каждая функция в этом коде.

Сведения об импорте и использовании

from threading import Thread
import time,requests,sys,os.path 
def usage():    
    print("----------USAGE INSTRUCTION ---------")    
    print(f"{sys.argv[0]} URL WORDLIST NUMBER_OF_THREADS")    
    sys.exit()

Эти стартовые строки Import the Threading and Other Modules. Функция Usage() показывает пользователю, как использовать программу, она срабатывает в случае недостаточного количества аргументов командной строки.

Подготовка фрагментов списка

def prepare(myList,numOfChunks):    
    for i in range(0, len(myList), numOfChunks):        
    yield myList[i:i + numOfChunks]

Эта функция реализует логику фрагментации, описанную в строках выше. Здесь в игру вступает количество потоков, в соответствии с которым мы разбиваем основной список.

Логика для многопоточности.

Мы определяем 2 функции brute() и worker(). Потоки будут начинаться с функции brute(), а функция worker() будет обрабатывать запросы.

threads.append(Thread(target=worker,args=(lists,url),daemon=True))

Мы возьмем списки из Chunked list и начнем с них Threads.

Потоки будут иметь рабочую функцию в качестве цели, они будут добавлены к списку, содержащему все потоки.

После этого все потоки будут запущены и присоединены.

for thread in threads:  
    thread.start()        
for thread in threads:        
    thread.join()
  • thread.start()

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

  • thread.join()

При вызове метода join() вызывающий поток блокируется до тех пор, пока объект потока (для которого этот поток вызывается) не будет завершен. Объекты потока могут завершиться при любом из следующих условий:

  • Либо нормально.
  • Через плохо обработанное исключение.
  • До тех пор, пока не произойдет необязательный тайм-аут.

Это помогает всем потокам завершить свою работу и завершиться нормально.

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

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

Примечания 📒

Это НЕполное руководство по многопоточности, здесь я только коснулся поверхности. Моя главная цель состояла в том, чтобы научить, как реализовать это в коде.

Я призываю вас узнать больше об этом в свободное время. Не стесняйтесь связаться со мной в любых сомнениях.

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

На этом все, и поделитесь своими баллами, если хотите, чтобы я добавил их сюда.

Большое спасибо за чтение. Поделитесь, если вам понравилось 😇😇

Подпишитесь на меня здесь, в Medium и Подпишитесь на список рассылки 💌, если хотите получать мои статьи, когда я их публикую.✨✨

Мой GitHub: MayankPandey01 👨‍💻

Вы можете найти меня в Твиттере: mayank_pandey01 👻

Больше контента на plainenglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Получите эксклюзивный доступ к возможностям написания и советам в нашем сообществе Discord.