Python Threading: Вступ – techukraine.net

У цьому підручнику ви дізнаєтеся, як використовувати вбудований модуль обробки потоків Python для вивчення можливостей багатопоточності в Python.

Починаючи з основ процесів і потоків, ви дізнаєтесь, як працює багатопотоковість у Python, одночасно розуміючи поняття паралельності та паралелізму. Потім ви дізнаєтесь, як запустити та запустити один або кілька потоків у Python за допомогою вбудованого модуля потоків.

Давайте розпочнемо.

Процеси проти потоків: у чому різниця?

Що таке процес?

Процес — це будь-який екземпляр програми, який потрібно запустити.

Це може бути що завгодно – сценарій Python або веб-браузер, як-от Chrome, до програми для відеоконференцій. Якщо ви запустите диспетчер завдань на своєму комп’ютері та перейдете до «Продуктивність» –> «ЦП», ви зможете побачити процеси та потоки, які зараз виконуються на ядрах вашого ЦП.

Розуміння процесів і потоків

Всередині процесу є спеціальна пам’ять, у якій зберігаються код і дані, що відповідають процесу.

Процес складається з одного або кількох потоків. Потік — це найменша послідовність інструкцій, яку може виконати операційна система, і він представляє потік виконання.

Кожен потік має власний стек і регістри, але не виділену пам’ять. Усі потоки, пов’язані з процесом, можуть отримати доступ до даних. Таким чином, дані та пам’ять спільно використовуються всіма потоками процесу.

У процесорі з N ядрами N процесів можуть виконуватися паралельно в один і той самий момент часу. Однак два потоки одного процесу ніколи не можуть виконуватися паралельно, але можуть виконуватися одночасно. У наступному розділі ми розглянемо концепцію паралелізму та паралелізму.

Базуючись на тому, що ми навчилися досі, давайте підсумуємо відмінності між процесом і потоком.

FeatureProcessThreadMemoryDedicated memoryShared memoryMode of executionParallel, concurrentConcurrent; але не parallelExecution, який обробляється інтерпретатором Operating SystemCPython

Багатопотоковість в Python

У Python глобальне блокування інтерпретатора (GIL) гарантує, що лише один потік може отримати блокування та запустити в будь-який момент часу. Усі потоки повинні отримати це блокування для запуску. Це гарантує, що лише один потік може бути у виконанні — у будь-який заданий момент часу — і дозволяє уникнути одночасної багатопоточності.

  Що таке дошка Microsoft і як її використовувати?

Наприклад, розглянемо два потоки, t1 і t2, одного процесу. Оскільки потоки спільно використовують ті самі дані, коли t1 зчитує певне значення k, t2 може змінити те саме значення k. Це може призвести до тупикових ситуацій і небажаних результатів. Але лише один з потоків може отримати блокування та працювати в будь-якому випадку. Тому GIL також забезпечує безпеку потоків.

Отже, як ми досягаємо можливостей багатопоточності в Python? Щоб зрозуміти це, давайте обговоримо поняття паралельності та паралелізму.

Паралелізм проти паралелізму: огляд

Розглянемо процесор з кількома ядрами. На малюнку нижче центральний процесор має чотири ядра. Це означає, що ми можемо виконувати чотири різні операції паралельно в будь-який момент.

Якщо є чотири процеси, то кожен із них може працювати незалежно й одночасно на кожному з чотирьох ядер. Припустимо, що кожен процес має два потоки.

Щоб зрозуміти, як працює потокова передача, давайте перейдемо з багатоядерної архітектури процесора на одноядерну. Як згадувалося, лише один потік може бути активним у певному екземплярі виконання; але ядро ​​процесора може перемикатися між потоками.

Наприклад, потоки, прив’язані до вводу-виводу, часто чекають операцій введення-виведення: читання введених користувачем даних, читання бази даних і файлові операції. Протягом цього часу очікування він може зняти блокування, щоб інший потік міг працювати. Час очікування також може бути простою операцією, такою як сплячий режим протягом n секунд.

Підсумовуючи: під час операцій очікування потік знімає блокування, дозволяючи ядру процесора переключитися на інший потік. Попередній потік відновлює виконання після закінчення періоду очікування. Цей процес, коли ядро ​​процесора перемикається між потоками одночасно, полегшує багатопотоковість. ✅

Якщо ви хочете реалізувати паралелізм на рівні процесу у своїй програмі, подумайте про використання натомість багатопроцесорності.

Модуль потоків Python: перші кроки

Python поставляється з модулем обробки потоків, який можна імпортувати в сценарій Python.

import threading

Щоб створити об’єкт потоку в Python, ви можете використовувати конструктор Thread: threading.Thread(…). Це загальний синтаксис, якого достатньо для більшості реалізацій потоків:

threading.Thread(target=...,args=...)

тут,

  • target — аргумент ключового слова, що позначає виклик Python
  • args — це кортеж аргументів, які приймає мета.
  Як об’єднати презентації PowerPoint

Щоб запустити приклади коду в цьому підручнику, вам знадобиться Python 3.x. Завантажте код і дотримуйтесь.

Як визначити та запустити потоки в Python

Давайте визначимо потік, який виконує цільову функцію.

Цільова функція some_func.

import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())

Давайте розберемо, що робить наведений вище фрагмент коду:

  • Він імпортує модулі потоків і часу.
  • Функція some_func має описові оператори print() і включає операцію сну протягом двох секунд: time.sleep(n) змушує функцію сплячи на n секунд.
  • Далі ми визначаємо потік thread_1 з метою some_func. threading.Thread(target=…) створює об’єкт потоку.
  • Примітка: вказуйте назву функції, а не виклик функції; використовуйте some_func, а не some_func().
  • Створення об’єкта потоку не запускає потік; викликає метод start() для об’єкта потоку.
  • Щоб отримати кількість активних потоків, ми використовуємо функцію active_count().

Сценарій Python виконується в основному потоці, і ми створюємо інший потік (thread1), щоб запустити функцію some_func, тому кількість активних потоків дорівнює двом, як видно з результату:

# Output
Running some_func...
2
Finished running some_func.

Якщо ми уважніше подивимось на вихідні дані, то побачимо, що після запуску thread1 виконується перший оператор друку. Але під час операції сну процесор перемикається на головний потік і друкує кількість активних потоків, не чекаючи завершення виконання потоку1.

Очікування завершення виконання потоків

Якщо ви хочете, щоб thread1 завершив виконання, ви можете викликати для нього метод join() після запуску потоку. Це призведе до очікування завершення виконання потоку1 без перемикання на головний потік.

import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())

Тепер thread1 завершив виконання, перш ніж ми роздрукуємо кількість активних потоків. Отже, запущено лише основний потік, що означає, що кількість активних потоків дорівнює одному. ✅

# Output
Running some_func...
Finished running some_func.
1

Як запускати кілька потоків у Python

Далі створимо два потоки для виконання двох різних функцій.

Тут count_down — це функція, яка приймає число як аргумент і веде відлік від цього числа до нуля.

def count_down(n):
    for i in range(n,-1,-1):
        print(i)

Ми визначаємо count_up, іншу функцію Python, яка рахує від нуля до заданого числа.

def count_up(n):
    for i in range(n+1):
        print(i)

📑 Під час використання функції range() із синтаксисом range(start, stop, step), кінцева точка зупинки виключається за умовчанням.

  Як створити гістограму в Microsoft Excel

– Щоб зворотній відлік від певного числа до нуля, ви можете використовувати від’ємне значення кроку -1 і встановити кінцеве значення на -1, щоб включити нуль.

– Подібним чином, щоб підрахувати до n, вам потрібно встановити кінцеве значення на n + 1. Оскільки значення за замовчуванням для початку та кроку дорівнюють 0 і 1 відповідно, ви можете використовувати діапазон (n + 1), щоб отримати послідовність 0 через п.

Далі ми визначаємо два потоки, thread1 і thread2 для виконання функцій count_down і count_up відповідно. Ми додаємо оператори друку та операції сну для обох функцій.

Створюючи об’єкти потоку, зауважте, що аргументи цільової функції мають бути вказані як кортеж — параметр args. Оскільки обидві функції (count_down і count_up) приймають один аргумент. Вам доведеться явно вставити кому після значення. Це гарантує, що аргумент усе ще передається як кортеж, оскільки наступні елементи виводяться як None.

import threading
import time

def count_down(n):
    for i in range(n,-1,-1):
        print("Running thread1....")
        print(i)
        time.sleep(1)


def count_up(n):
    for i in range(n+1):
        print("Running thread2...")
        print(i)
        time.sleep(1)

thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()

У вихідних даних:

  • Функція count_up виконується в потокі2 і рахує до 5, починаючи з 0.
  • Функція count_down виконується в потокі1, відраховуючи від 10 до 0.
# Output
Running thread1....
10
Running thread2...
0
Running thread1....
9
Running thread2...
1
Running thread1....
8
Running thread2...
2
Running thread1....
7
Running thread2...
3
Running thread1....
6
Running thread2...
4
Running thread1....
5
Running thread2...
5
Running thread1....
4
Running thread1....
3
Running thread1....
2
Running thread1....
1
Running thread1....
0

Ви бачите, що thread1 і thread2 виконуються по черзі, оскільки обидва включають операцію очікування (сон). Коли функція count_up завершує підрахунок до 5, thread2 більше не активний. Отже, ми отримуємо результат, що відповідає лише потоку1.

Підводячи підсумки

У цьому підручнику ви дізналися, як використовувати вбудований модуль потокової обробки Python для реалізації багатопоточності. Ось короткий виклад основних висновків:

  • Конструктор потоку можна використовувати для створення об’єкта потоку. Використання threading.Thread(target=,args=()) створює потік, який запускає цільовий виклик із аргументами, указаними в args.
  • Програма Python працює в основному потоці, тому об’єкти потоку, які ви створюєте, є додатковими потоками. Ви можете викликати функцію active_count(), яка повертає кількість активних потоків у будь-якому екземплярі.
  • Ви можете запустити потік за допомогою методу start() для об’єкта потоку та дочекатися завершення виконання за допомогою методу join().

Ви можете закодувати додаткові приклади, налаштувавши час очікування, спробувавши іншу операцію введення-виведення тощо. Обов’язково реалізуйте багатопотоковість у ваших майбутніх проектах Python. Щасливого кодування!🎉