1

id()Python サブインタープリターの初期化 (Python/C API から) と Python関数の内部動作について質問があります。より正確には、WSGI Python コンテナー (Apache の nginx および mod_wsgi で使用される uWSGI など) でのグローバル モジュール オブジェクトの処理についてです。

次のコードは、上記の両方の環境で期待どおりに (分離して) 動作しますが、関数が実行されるプロセス/サブインタープリターに関係なく、関数が常に変数ごとに同じ値を返す理由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]

1 つのマスター プロセスと 2 つのワーカーを使用して、uWSGI でこのコードをテストしました。mod_wsgi では、プロセスごとに 2 つのプロセスと 1 つのスレッドを持つデーモン モードを使用します。一般的な出力は次のとおりです。

pid = 15278
id(A) = 139748093678128
id(a) = 139748093962360
str(a) = 1

最初のロード時、次に:

pid = 15282 id
(A) = 139748093678128
id(a) = 139748093962360
str(a) = 1

2番目に、次に

ピッド = 15278 | pid = 15282 id
(A) = 139748093678128
id(a) = 139748093962360
str(a) = 2

お互いに。ご覧のとおりid()、クラスとクラス インスタンスの両方の (メモリの場所) は、両方のプロセス (上記の最初/2 番目のロード) で同じままですが、同時にクラス インスタンスは別のコンテキストに存在します (そうしないと、2 番目の要求が表示されます)。 「1」ではなく「2」)!

答えはPythonのドキュメントにヒントがあると思います:

id(object):

オブジェクトの「アイデンティティ」を返します。これは、このオブジェクトの有効期間中に一意で一定であることが保証されている整数 (または長整数) です。有効期間が重複しない 2 つのオブジェクトは、同じid()値を持つ場合があります。

しかし、それが本当にその理由である場合、id()値がオブジェクトのアドレスであると主張する次のステートメントに悩まされています!

これが、サードパーティの拡張モジュールでオブジェクト参照 (ポインタ) をキャッシュする問題を解決 (またはむしろ修正)する Python/C API の「巧妙な」機能である可能性が非常に高いという事実を高く評価していますが、それでもこの動作には一貫性がないことがわかります。と... まあ、常識。誰かがこれを説明してもらえますか?

また、mod_wsgi は各プロセスでモジュールをインポートする (つまり2 回) ことに気付きましたが、uWSGI は両方のプロセスでモジュールを1回だけインポートします。uWSGI マスター プロセスがインポートを行うため、そのコンテキストのコピーを子プロセスにシードすると思います。その後、両方のワーカーが独立して動作し (ディープ コピー?)、同時に同じオブジェクト アドレスを使用しているように見えます。(また、ワーカーはリロード時に元のコンテキストに再初期化されます。)

このような長い投稿で申し訳ありませんが、十分な詳細を提供したかったのです。ありがとうございました!

4

2 に答える 2

2

あなたが何を求めているのかは完全には明らかではありません。質問がより具体的であれば、より簡潔な答えが得られます。

まず、オブジェクトの id は、実際には (少なくとも CPython では) メモリ内のアドレスです。これはまったく正常なことです。同じプロセス内の 2 つのオブジェクトが同時にアドレスを共有することはできず、オブジェクトのアドレスは CPython では変更されないため、アドレスは ID として適切に機能します。これがどのように常識に違反しているのかわかりません。

次に、バックエンド プロセスは 2 つの非常に異なる方法で生成される可能性があることに注意してください。

  • 一般的な WSGI バックエンド ハンドラーがプロセスを fork し、各プロセスがバックエンドを開始します。これは単純で言語に依存しませんが、多くのメモリを浪費し、バックエンド コードを繰り返しロードする時間を浪費します。
  • より高度なバックエンドでは、Python コードが一度読み込まれ、読み込まれた後にサーバーのコピーがフォークされます。これにより、コードが 1 回だけ読み込まれるため、はるかに高速になり、メモリの無駄が大幅に削減されます。これが本番品質の WSGI サーバーの仕組みです。

ただし、これらの両方の場合の最終結果は同じで、別々のフォークされたプロセスになります。

では、なぜ同じ ID で終わるのでしょうか。それは、上記の方法のどれが使用されているかによって異なります。

  • 一般的な WSGI ハンドラーでは、各プロセスが本質的に同じことを行っているため、単純に発生しています。プロセスが同じことをしている限り、最終的に同じ ID になる傾向があります。ある時点でそれらは発散し、これはもはや起こりません。
  • プリロード バックエンドでは、サーバーがフォークする前にこの初期コードが 1 回だけ発生するため、同じ ID を持つことが保証されているため、これが発生しています。

ただし、いずれにしても、フォークが発生すると、それらは別々のオブジェクトであり、別々のコンテキストにあります。同じ ID を持つ別のプロセスのオブジェクトには意味がありません。

于 2010-11-10T17:11:20.477 に答える
2

これは、デモンストレーションによって簡単に説明できます。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.

編集: mod_wsgi が 2 回インポートされる理由は、フォークではなく python を呼び出すことでさらにプロセスを作成するためだと思います。uwsgi のアプローチは、より少ないメモリを使用できるため、優れています。fork のページ共有は COW (copy on write) です。

于 2010-11-10T17:31:28.957 に答える