Используют ли потоки (threads) в Python общую память

Ответ

Да, потоки в Python, созданные с помощью модуля threading, работают в рамках одного процесса и используют общее адресное пространство. Это означает, что они имеют доступ к одним и тем же данным и объектам в памяти, включая глобальные переменные.

Ключевые особенности:

  1. Эффективность для I/O-bound задач: Потоки отлично подходят для задач, где программа ожидает внешние ресурсы (например, сетевой ответ или чтение с диска), так как один поток может выполняться, пока другой заблокирован в ожидании.
  2. Проблема GIL (Global Interpreter Lock): В стандартной реализации CPython GIL не позволяет нескольким потокам одновременно выполнять Python-байткод. Это делает потоки неэффективными для параллельных вычислений на многоядерных процессорах (CPU-bound задач).
  3. Состояние гонки (Race Condition): Из-за общего доступа к памяти одновременное изменение данных несколькими потоками может привести к непредсказуемым результатам. Для предотвращения этого необходимо использовать примитивы синхронизации.

Пример состояния гонки:

import threading

# Общий ресурс
counter = 0

def increment():
    global counter
    for _ in range(100000):
        counter += 1

threads = [threading.Thread(target=increment) for _ in range(5)]

for t in threads:
    t.start()

for t in threads:
    t.join()

# Ожидаемый результат: 500000
# Реальный результат будет меньше из-за состояния гонки
print(f"Финальное значение счетчика: {counter}")

Решение с использованием Lock:

Для обеспечения атомарности операции инкремента используется блокировка threading.Lock.

import threading

counter = 0
lock = threading.Lock()

def safe_increment():
    global counter
    for _ in range(100000):
        with lock: # Блокировка захватывается перед изменением
            counter += 1
        # Блокировка автоматически освобождается при выходе из блока 'with'

# ... (аналогичный запуск потоков)

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

Ответ 18+ 🔞

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

Что тут важно, блядь, понимать:

  1. Для ожидания — самое то. Если твоя программа, например, сайты дергает или файлы читает — там же простои, блядь, на каждом шагу. Так вот один поток может ждать ответа от сервера, а другой в это время свои дела делать. Красота, ёпта!
  2. А вот GIL, сука, портит всю малину. Эта хуйня, Global Interpreter Lock, в CPython не дает нескольким потокам настоящий Питон-код одновременно выполнять. Так что если у тебя задачи чисто вычислительные, на ядра нагрузить — забудь, нихуя не выйдет. Они по очереди, как в очереди за колбасой, будут работать.
  3. Драка за данные — наше всё. Раз память общая, то два потока могут в одну переменную одновременно тыкаться. Это пиздец, товарищ. Состояние гонки называется. Результат будет непредсказуемый, как погода в ноябре.

Вот, смотри, как они могут друг другу всё сломать:

import threading

# Общий ресурс, одна тарелка на всех
counter = 0

def increment():
    global counter
    for _ in range(100000):
        counter += 1 # Сейчас один поток прочитал, а другой уже в это время своё дерьмо пишет!

threads = [threading.Thread(target=increment) for _ in range(5)]

for t in threads:
    t.start()

for t in threads:
    t.join()

# В теории должно быть 500000, а на деле — хуй знает что.
# Каждый раз разное число, меньше нужного. Потому что все лезут, как охуевшие.
print(f"Финальное значение счетчика: {counter}")

А вот как надо, с замком!

Чтобы не было этой драки, нужно поставить турникет, threading.Lock называется. Кто с замком — тот в туалет заходит, остальные ждут.

import threading

counter = 0
lock = threading.Lock() # Вот этот самый турникет, блядь

def safe_increment():
    global counter
    for _ in range(100000):
        with lock: # Захватил замок — иди работай, царь
            counter += 1
        # Вышел из 'with' — отпустил замок, следующий заходи

# ... (потоки запускаются так же)

Вот так, с этим lock, только один ушлёпок в каждый момент времени к переменной прикасается. Порядок, блядь, а не бардак. Результат всегда будет чёткий, 500000, как миленький.