Модуль threading

Відкрита

2.1 Модуль threading

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

Основні поняття багатопоточності в Python:

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

Процес — це екземпляр програми, що виконується в операційній системі, з власним адресним простором і ресурсами. На відміну від потоків, процеси ізольовані один від одного і обмінюються даними через міжпроцесну взаємодію (IPC).

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

Важливо! Зважай, що через Global Interpreter Lock (GIL) багатопоточність у Python може не забезпечити значного збільшення продуктивності для обчислювально інтенсивних завдань, оскільки GIL запобігає одночасному виконанню декількох потоків Python на багатоядерних процесорах.

Модуль threading

Модуль threading в Python надає високорівневий інтерфейс для роботи з потоками. Він дозволяє створювати і керувати потоками, синхронізувати їх та організовувати взаємодію між ними. Давай розглянемо ключові компоненти і функції цього модуля більш детально.

Основні компоненти модуля threading

Сутності для роботи з потоками:

  • Thread — основний клас для створення і керування потоками.
  • Timer — таймер для виконання функції після закінчення заданого часу.
  • ThreadLocal — дозволяє створювати дані, локальні для потоку.

Механізм синхронізації потоків:

  • Lock — примітив синхронізації для запобігання одночасного доступу до спільних ресурсів.
  • Condition — умовна змінна для більш складної синхронізації потоків.
  • Event — примітив для повідомлення між потоками.
  • Semaphore — примітив для обмеження кількості потоків, які можуть одночасно виконувати певну ділянку.
  • Barrier — синхронізує задану кількість потоків, блокуючи їх до тих пір, поки всі вони не досягнуть бар'єра.

Нижче я розповім про 3 класи для роботи з потоками, механізм же синхронізації потоків вам найближчим часом не знадобиться.

2.2 Клас Thread

Клас Thread — основний клас для створення і керування потоками. У нього є 4 основних методи:

  • start(): Запускає виконання потоку.
  • join(): Поточний потік призупиняється і чекає завершення запущеного потоку.
  • is_alive(): Повертає True, якщо потік виконується.
  • run(): Метод, що містить код, який буде виконуватися в потоці. Перевизначається при наслідуванні від класу Thread.

Все значно простіше, ніж здається — приклад використання класу Thread.

Запуск простого потоку

import threading

def worker():
    print("Worker thread is running")

# Створення нового потоку
t = threading.Thread(target=worker) # створили новий об'єкт потоку
t.start() # Запустили потік
t.join() # Очікуємо завершення потоку
print("Main thread is finished")

Після виклику методу start, функція worker почне своє виконання. Або, якщо точніше, її потік буде додано в список активних потоків.

Використання аргументів

import threading

def worker(number, text):
    print(f"Worker {number}: {text}")

# Створення нового потоку з аргументами
t = threading.Thread(target=worker, args=(1, "Hello"))
t.start()
t.join()

Для передачі параметрів у новий потік потрібно просто вказати їх у вигляді кортежу і присвоїти параметру args. При виклику функції, яка була вказана у target, параметри будуть передані автоматично.

Перевизначення методу run

import threading

class MyThread(threading.Thread):
    def run(self):
        print("Custom thread is running")

# Створення і запуск потоку
t = MyThread()
t.start()
t.join()

Є два способи вказати функцію, з якої потрібно почати виконання нового потоку — можна передати її через параметр target при створенні об'єкта Thread, або ж наслідуватися від класу Thread і перевизначити функцію run. Обидва способи вважаються легальними і використовуються постійно.

2.3 Клас Timer

Клас Timer в модулі threading призначений для запуску функції через заданий проміжок часу. Цей клас корисний для виконання відкладених завдань у багатопоточному середовищі.

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

  • Метод start() запускає таймер, який відраховує заданий проміжок часу, а потім викликає вказану функцію.
  • Метод cancel() дозволяє зупинити таймер, якщо він ще не спрацював. Це корисно для запобігання виконанню функції, якщо таймер більше не потрібен.

Приклади використання:

Запуск функції з затримкою

У цьому прикладі функція hello буде викликана через 5 секунд після запуску таймера.

import threading

def hello():
    print("Hello, world!")

# Створення таймера, який викличе функцію hello через 5 секунд
t = threading.Timer(5.0, hello)
t.start()  # Запуск таймера

Зупинка таймера до виконання

Тут таймер буде зупинено до того, як функція hello встигне виконатися, і, відповідно, нічого не буде надруковано.

import threading

def hello():
    print("Hello, world!")

# Створення таймера
t = threading.Timer(5.0, hello)
t.start()  # Запуск таймера

# Зупинка таймера до виконання
t.cancel()

Таймер з аргументами

У цьому прикладі таймер викличе функцію greet через 3 секунди і передасть їй аргумент "Alice".

import threading

def greet(name):
    print(f"Hello, {name}!")

# Створення таймера з аргументами
t = threading.Timer(3.0, greet, args=["Alice"])
t.start()

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

2.4 Клас ThreadLocal

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

Кожен потік, що використовує ThreadLocal, матиме свої власні незалежні копії даних. Дані, збережені в об'єкті ThreadLocal, унікальні для кожного потоку і не розділяються з іншими потоками. Це зручно для зберігання даних, які використовуються лише в контексті одного потоку, таких як поточний користувач у веб-додатку або поточне з'єднання з базою даних.

Приклади використання:

Основне використання

У цьому прикладі кожен потік присвоює своє ім'я локальній змінній value і виводить його. Значення value унікально для кожного потоку.

import threading

# Створення об'єкта ThreadLocal
local_data = threading.local()

def process_data():
    # Присвоєння значення локальної змінної потоку
    local_data.value = threading.current_thread().name
    # Доступ до локальної змінної потоку
    print(f'Value in {threading.current_thread().name}: {local_data.value}')

threads = []
for i in range(5):
    t = threading.Thread(target=process_data)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Зберігання даних користувача у веб-додатку

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

import threading
# Створення об'єкта ThreadLocal
user_data = threading.local()
def process_request(user):
    # Присвоєння значення локальної змінної потоку
    user_data.user = user
    handle_request()

def handle_request():
    # Доступ до локальної змінної потоку
    print(f'Handling request for user: {user_data.user}')

threads = []
users = ['Alice', 'Bob', 'Charlie']
for user in users:
    t = threading.Thread(target=process_request, args=(user,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Це були 3 найкорисніших класи бібліотеки threading. Скоріше за все, ви будете використовувати їх у своїй роботі, а ось інші класи — навряд чи. Зараз всі переходять на асинхронні функції та бібліотеку asyncio. Ось про неї ми і будемо говорити все найближчий час.

2
Задача
Модуль 1: Python Core,  14 рівень1 лекція
Недоступна
Використання класу Timer
Використання класу Timer
2
Задача
Модуль 1: Python Core,  14 рівень1 лекція
Недоступна
Використання ThreadLocal
Використання ThreadLocal
Коментарі
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
Для цієї сторінки немає коментарів.