代码之家  ›  专栏  ›  技术社区  ›  randomir

子解释器中全局Python对象void的唯一性?

  •  1
  • randomir  · 技术社区  · 14 年前

    id() 功能。更准确地说,是关于在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]
    

    我已经用一个主进程和两个工人在uWSGI中测试了这段代码;在mod_wsgi中使用了一个deamon模式,每个进程有两个进程和一个线程。典型输出为:

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

    pid=15282

    id(a)=139748093962360
    str(a)=一

    pid=15278 | pid=15282
    id(A)=139748093678128

    str(a)=2

    互相攻击。如你所见, id() 类和类实例的(内存位置)在 二者都 进程(上面的第一次/第二次加载),同时类实例位于单独的上下文中(否则第二次请求将显示“2”而不是“1”)!

    id(object) :

    返回对象的标识。这是一个整数(或长整数),它 在该对象的生存期内保证其唯一性和常量。二 id() 价值。

    id() 值是对象的地址!

    虽然我很欣赏这样一个事实:这很可能只是一个Python/C API“聪明”的特性,它可以解决(或者更确切地说 修复 )一个 problem of caching object references (pointers) in 3rd party extension modules ,我仍然觉得这种行为与。。。嗯,常识。有人能解释一下吗?

    我还注意到mod_wsgi在每个进程中导入模块(即。 ),而uWSGI只导入模块 一旦 对于两个过程。由于uWSGI主进程执行导入,我想它为子进程播下了该上下文的副本。两名工人事后都独立工作(深抄袭?),同时使用相同的对象地址,似乎。(此外,工作进程在重新加载时会重新初始化到原始上下文。)

    我为这么长的帖子道歉,但我想提供足够的细节。 谢谢您!

    2 回复  |  直到 14 年前
        1
  •  2
  •   Glenn Maynard    14 年前

    你的问题还不完全清楚;如果问题更具体,我会给出一个更简洁的答案。

    接下来,请注意,后端进程可能以两种截然不同的方式生成:

    • 一个通用的WSGI后端处理程序将分叉进程,然后每个进程将启动一个后端。这很简单,而且与语言无关,但会浪费大量内存和重复加载后端代码的时间。

    然而,这两种情况的最终结果是相同的:分离的分叉进程。

    • 对于一个通用的WSGI处理程序,它的发生仅仅是因为每个进程都在做本质上相同的事情。只要进程在做相同的事情,它们最终将倾向于使用相同的id;在某个点上,它们将发生分歧,而这将不再发生。
    • 对于预加载后端,这是因为这个初始代码只在服务器分叉之前发生一次,所以它保证具有相同的ID。

    不管怎样 ,一旦发生fork,它们就是独立的对象,在不同的上下文中。对于具有相同ID的单独进程中的对象没有意义。

        2
  •  2
  •   dan_waterworth    14 年前

    这很容易通过演示来解释。你看,当uwsgi创建一个新进程时,它会分叉解释器。现在,fork具有有趣的内存属性:

    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'
    

    编辑:我怀疑mod_wsgi两次导入的原因是它通过调用python而不是forking来创建更多的进程。uwsgi的方法更好,因为它可以使用更少的内存。fork的页面共享是COW(copy-on-write)。