Уникальность глобальных объектов Python, недействительных в суб-интерпретаторах? - PullRequest
1 голос
/ 10 ноября 2010

У меня есть вопрос о внутренней работе инициализации суб-интерпретатора Python (из Python / C API) и функции Python id(). Точнее, об обработке глобальных объектов модуля в контейнерах WSGI Python (например, uWSGI, используемый с nginx и mod_wsgi в Apache).

Следующий код работает как ожидалось (изолированно) в обеих упомянутых средах, но я не могу объяснить себе , почему функция id() всегда возвращает одно и то же значение для каждой переменной, независимо от процесс / суб-интерпретатор, в котором он выполняется.

from __future__ import print_function
import os, sys

def log(*msg):
    print(">>>", *msg, file=sys.stderr)

class A:
    def __init__(self, x):
        self.x = x
    def __str__(self):
        return self.x
    def set(self, x):
        self.x = x

a = A("one")
log("class instantiated.")

def application(environ, start_response):

    output = "pid = %d\n" % os.getpid()
    output += "id(A) = %d\n" % id(A)
    output += "id(a) = %d\n" % id(a)
    output += "str(a) = %s\n\n" % a

    a.set("two")

    status = "200 OK"
    response_headers = [
        ('Content-type', 'text/plain'), ('Content-Length', str(len(output)))
    ]
    start_response(status, response_headers)

    return [output]

Я проверил этот код в uWSGI с одним основным процессом и двумя рабочими; и в mod_wsgi, используя режим deamon с двумя процессами и одним потоком на процесс. Типичный вывод:

pid = 15278
id (A) = 139748093678128
id (a) = 139748093962360
str (a) = один

при первой загрузке, затем:

pid = 15282
id (A) = 139748093678128
id (a) = 139748093962360
str (a) = один

на секунду, а затем

pid = 15278 | pid = 15282
id (A) = 139748093678128
id (a) = 139748093962360
ул (а) = два

на всех остальных. Как вы можете видеть, id() (место в памяти) как класса, так и экземпляра класса остается одинаковым в обоих процессах (первая / вторая загрузка выше), в то время как экземпляры класса одновременно живут в отдельный контекст (в противном случае второй запрос будет показывать «два» вместо «один»)!

Я подозреваю, что ответ может быть подсказан Python docs:

id(object)

Возвращает «идентичность» объекта. Это целое число (или длинное целое), которое гарантированно будет уникальным и постоянным для этого объекта в течение срока его службы. Два объекты с неперекрывающимися временами жизни могут иметь одинаковое значение id().

Но если это действительно является причиной, меня беспокоит следующее утверждение, которое утверждает, что значение id() является адресом объекта!

Хотя я ценю тот факт, что это вполне может быть просто «умная» функция Python / C API, которая решает (или, скорее, исправляет ) проблему кэширования ссылок на объекты (указатели) в 3-м модули расширения партии , я все еще нахожу это поведение несовместимым с ... ну, здравым смыслом. Может ли кто-нибудь объяснить это?

Я также заметил, что mod_wsgi импортирует модуль в каждом процессе (т.е. дважды ), в то время как uWSGI импортирует модуль только один раз для обоих процессов. Поскольку основной процесс uWSGI выполняет импорт, я полагаю, что он заполняет дочерние элементы копиями этого контекста. После этого оба работника работают независимо (глубокое копирование?), Одновременно используя, по-видимому, одни и те же адреса объектов. (Кроме того, работник перезагружается в исходный контекст после перезагрузки.)

Я прошу прощения за такой длинный пост, но я хотел дать достаточно подробностей. Спасибо!

Ответы [ 2 ]

2 голосов
/ 10 ноября 2010

Это легко объяснить с помощью демонстрации.Видите ли, когда uwsgi создает новый процесс, он разветвляет интерпретатор.Теперь у вилок есть интересные свойства памяти:

import os, time

if os.fork() == 0:
    print "child first " + str(hex(id(os)))
    time.sleep(2)
    os.attr = 'test'
    print "child second " + str(hex(id(os)))
else:
    time.sleep(1)
    print "parent first " + str(hex(id(os)))
    time.sleep(2)
    print "parent second " + str(hex(id(os)))
    print os.attr

Вывод:

child first 0xb782414cL
parent first 0xb782414cL
child second 0xb782414cL
parent second 0xb782414cL
Traceback (most recent call last):
  File "test.py", line 13, in <module>
    print os.attr
AttributeError: 'module' object has no attribute 'attr'

Хотя кажется, что объекты находятся в одном и том же адресе памяти, это разные объекты, но это не Python, но os.

edit: я подозреваю, что причина, по которой mod_wsgi импортирует дважды, заключается в том, что он создает дальнейшие процессы, вызывая python, а не разветвляясь.Подход uwsgi лучше, потому что он может использовать меньше памяти.Общий доступ к странице форка - COW (копирование при записи).

1 голос
/ 10 ноября 2010

Не совсем понятно, о чем вы спрашиваете;Я бы дал более краткий ответ, если бы вопрос был более конкретным.

Во-первых, идентификатор объекта на самом деле - по крайней мере в CPython - его адрес в памяти.Это совершенно нормально: два объекта в одном и том же процессе одновременно не могут совместно использовать адрес, и адрес объекта никогда не меняется в CPython, поэтому адрес работает аккуратно как идентификатор.Я не знаю, как это нарушает здравый смысл.

Далее, обратите внимание, что бэкэнд-процесс может порождаться двумя совершенно разными способами:

  • Универсальный бэкэнд-обработчик WSGI будет форкать процессы, а затем каждый из процессов запустит бэкэнд.Это просто и не зависит от языка, но тратит много памяти и тратит много времени на загрузку кода бэкенда.
  • Более продвинутый бэкэнд загрузит код Python один раз, а затем разгрузит копии сервера после его загрузки,Это приводит к тому, что код загружается только один раз, что намного быстрее и значительно снижает потери памяти.Вот как работают серверы WSGI производственного качества.

Однако конечный результат в обоих этих случаях одинаков: отдельные раздвоенные процессы.

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

  • При использовании универсального обработчика WSGI это происходит просто потому, что каждый процесс выполняет по существу одно и то же.Пока процессы делают одно и то же, они будут иметь одинаковые идентификаторы;в какой-то момент они будут расходиться, и это больше не произойдет.
  • С бэкэндом с предварительной загрузкой это происходит потому, что этот исходный код происходит только один раз, перед тем, как сервер разветвляется, поэтому он гарантированно имеет тот же идентификатор.

Однако, в любом случае , когда вилка происходит, это отдельные объекты в разных контекстах.Нет никакого значения для объектов в отдельных процессах, имеющих одинаковый идентификатор.

...