Ответ
В Python работа с потоками реализована через модуль threading. Ключевой особенностью является наличие GIL (Global Interpreter Lock) — механизма, который позволяет только одному потоку исполнять Python-байт-код в один момент времени в рамках одного процесса.
Последствия GIL:
- Для I/O-bound задач (работа с сетью, файлами): Потоки эффективны. Пока один поток ждет ответа от сети, GIL освобождается, и другой поток может выполнять свою работу.
- Для CPU-bound задач (вычисления): Потоки не дают прироста производительности на многоядерных системах, так как только одно ядро может быть утилизировано в один момент времени. Для таких задач следует использовать модуль
multiprocessing.
Пример создания потока:
import threading
import time
def worker(name):
print(f"Поток {name} запущен")
time.sleep(1)
print(f"Поток {name} завершен")
# Создаем и запускаем поток
thread = threading.Thread(target=worker, args=("Worker-1",))
thread.start()
print("Главный поток продолжает работу")
# Ожидаем завершения дочернего потока
thread.join()
print("Программа завершена")
Ключевые моменты:
- Общая память: Потоки разделяют общее пространство памяти, что упрощает обмен данными, но требует механизмов синхронизации (
Lock,Semaphore) для предотвращения состояния гонки. - Демонические потоки: Потоки, созданные с флагом
daemon=True, автоматически завершаются при выходе из основного потока.
Ответ 18+ 🔞
А, ну вот, опять про эти ваши потоки в Python! Слушай, история как в анекдоте: приходит мужик в спортзал, а ему говорят — у нас тут только одна штанга на всех, и держать её можно только по очереди. Это пиздец, а не качалка! Так вот, GIL — это и есть та самая единственная штанга, блядь.
Представь: у тебя восьмиядерный процессор, мощный такой, а Python смотрит на него и говорит: "О, ёпта, восемь ядер! Отлично! Я буду использовать... одно. Потому что у меня GIL". И ты сидишь, такой: "Ну как так-то, нахуй?"
Но не всё так плохо, как кажется. Если твоя задача — тупо долбить процессор вычислениями (типа перемножай матрицы до посинения), то потоки тебе нихуя не помогут. Тут только один выход — multiprocessing, то есть запускать отдельные процессы. У каждого своя память, свой GIL, своя жизнь. Хуй с ним, что данные между ними таскать сложнее — зато все ядра в деле!
А вот если твоя программа больше болтается, чем работает — ждёт ответа от базы, качает файлы из интернета, читает с диска — вот тут потоки просто красота! Пока один поток спит, мечтая о пинге от сервера, GIL отпускает штангу, и другой поток её хватает. Все довольны, все при деле.
Смотри, как это выглядит в коде, простейший пример:
import threading
import time
def worker(name):
print(f"Поток {name} запущен")
time.sleep(1) # Имитируем какую-нибудь долгую операцию ввода-вывода
print(f"Поток {name} завершен")
# Создаем и запускаем поток
thread = threading.Thread(target=worker, args=("Worker-1",))
thread.start()
print("Главный поток продолжает работу")
# Ожидаем завершения дочернего потока
thread.join()
print("Программа завершена")
Видишь? Создали поток, запустили, а главная программа пошла дальше делать свои дела. Красота!
Но есть и подводный камень, ёпта! Все потоки живут в одной квартире, то есть в одной памяти. Это удобно — можно данные на столе оставить, и другой поток их увидит. Но представь, если два потока одновременно потянутся к последнему печенью в вазочке? Начнётся драка, состояние гонки, всё поломается. Поэтому для таких случаев есть замки (Lock) и прочие семафоры — чтобы друг другу руки не отбивали.
А ещё есть демоны (daemon=True). Это как потоки-камикадзе. Завершается главный поток — и все демоны автоматом накрываются медным тазом, без вопросов. Удобно для фоновой работы, которую не жалко.
Короче, инструмент мощный, но со своей спецификой. Главное — понять, когда он нужен, а когда от него один геморрой.